more RESTful APIs

This commit is contained in:
Domingo Dirutigliano
2025-02-11 19:11:30 +01:00
parent 49fea55bc7
commit f3ba6dc716
18 changed files with 378 additions and 163 deletions

View File

@@ -14,11 +14,10 @@ RUN bun run build
#Building main conteiner
FROM --platform=$TARGETARCH debian:trixie-slim AS base
RUN apt-get update -qq && apt-get upgrade -qq && \
apt-get install -qq python3-pip build-essential \
libnetfilter-queue-dev libnfnetlink-dev libmnl-dev libcap2-bin\
nftables libvectorscan-dev libtins-dev python3-nftables
FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest
RUN dnf -y update && dnf install -y python3-pip @development-tools gcc-c++ \
libnetfilter_queue-devel libnfnetlink-devel libmnl-devel libcap-ng-utils \
nftables vectorscan-devel libtins-devel python3-nftables libpcap-devel boost-devel
RUN mkdir -p /execute/modules
WORKDIR /execute
@@ -28,7 +27,7 @@ RUN pip3 install --no-cache-dir --break-system-packages -r /execute/requirements
COPY ./backend/binsrc /execute/binsrc
RUN g++ binsrc/nfqueue.cpp -o modules/cppqueue -std=c++23 -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libhs libmnl)
RUN g++ binsrc/nfproxy-tun.cpp -o modules/cppnfproxy -std=c++23 -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libmnl)
#RUN g++ binsrc/nfproxy-tun.cpp -o modules/cppnfproxy -std=c++23 -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libmnl)
COPY ./backend/ /execute/
COPY --from=frontend /app/dist/ ./frontend/

View File

@@ -1,20 +1,14 @@
#include <linux/netfilter/nfnetlink_queue.h>
#include <libnetfilter_queue/libnetfilter_queue.h>
#include <linux/netfilter/nfnetlink_conntrack.h>
#include <tins/tins.h>
#include <tins/tcp_ip/stream_follower.h>
#include <tins/tcp_ip/stream_identifier.h>
#include <libmnl/libmnl.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/types.h>
#include <stdexcept>
#include <thread>
#include <hs.h>
#include <iostream>
using Tins::TCPIP::Stream;
using Tins::TCPIP::StreamFollower;
using namespace std;
#ifndef NETFILTER_CLASS_CPP

View File

@@ -133,10 +133,6 @@ class SocketTunnelQueue: public NfQueueExecutor {
SocketTunnelQueue(int queue) : NfQueueExecutor(queue, &queue_cb) {}
~SocketTunnelQueue() {
// TODO
}
};
#endif // PROXY_TUNNEL_CPP

View File

@@ -130,6 +130,7 @@ class FirewallManager:
def allow_dhcp(self):
return self.db.get("allow_dhcp", "1") == "1"
@drop_invalid.setter
def allow_dhcp_set(self, value):
@allow_dhcp.setter
def allow_dhcp(self, value):
self.db.set("allow_dhcp", "1" if value else "0")

View File

@@ -8,6 +8,8 @@ import traceback
from utils import DEBUG
from fastapi import HTTPException
#TODO copied file, review
nft = FiregexTables()
class RegexFilter:

View File

@@ -4,6 +4,8 @@ from modules.nfregex.nftables import FiregexTables, FiregexFilter
from modules.nfregex.models import Regex, Service
from utils.sqlite import SQLite
#TODO copied file, review
class STATUS:
STOP = "stop"
ACTIVE = "active"

View File

@@ -1,5 +1,3 @@
import base64
class Service:
def __init__(self, service_id: str, status: str, port: int, name: str, proto: str, ip_int: str, **other):
self.id = service_id
@@ -14,17 +12,14 @@ class Service:
return cls(**var)
class Regex:
def __init__(self, regex_id: int, regex: bytes, mode: str, service_id: str, blocked_packets: int, is_case_sensitive: bool, active: bool, **other):
self.regex = regex
self.mode = mode
self.service_id = service_id
class PyFilter:
def __init__(self, filter_id:int, name: str, blocked_packets: int, edited_packets: int, active: bool, **other):
self.filter_id = filter_id
self.name = name
self.blocked_packets = blocked_packets
self.id = regex_id
self.is_case_sensitive = is_case_sensitive
self.edited_packets = edited_packets
self.active = active
@classmethod
def from_dict(cls, var: dict):
var['regex'] = base64.b64decode(var['regex'])
return cls(**var)

View File

@@ -71,7 +71,7 @@ async def get_settings():
"""Get the firewall settings"""
return firewall.settings
@app.post("/settings/set", response_model=StatusMessageModel)
@app.put("/settings", response_model=StatusMessageModel)
async def set_settings(form: FirewallSettings):
"""Set the firewall settings"""
firewall.settings = form
@@ -86,13 +86,13 @@ async def get_rule_list():
"enabled": firewall.enabled
}
@app.get('/enable', response_model=StatusMessageModel)
@app.post('/enable', response_model=StatusMessageModel)
async def enable_firewall():
"""Request enabling the firewall"""
firewall.enabled = True
return await apply_changes()
@app.get('/disable', response_model=StatusMessageModel)
@app.post('/disable', response_model=StatusMessageModel)
async def disable_firewall():
"""Request disabling the firewall"""
firewall.enabled = False
@@ -128,9 +128,9 @@ def parse_and_check_rule(rule:RuleModel):
return rule
@app.post('/rules/set', response_model=StatusMessageModel)
@app.post('/rules', response_model=StatusMessageModel)
async def add_new_service(form: RuleFormAdd):
"""Add a new service"""
"""Edit rule table"""
rules = [parse_and_check_rule(ele) for ele in form.rules]
try:
db.queries(["DELETE FROM rules"]+

260
backend/routers/nfproxy.py Normal file
View File

@@ -0,0 +1,260 @@
import secrets
import sqlite3
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from modules.nfproxy.nftables import FiregexTables
from modules.nfproxy.firewall import STATUS, FirewallManager
from utils.sqlite import SQLite
from utils import ip_parse, refactor_name, socketio_emit, PortType
from utils.models import ResetRequest, StatusMessageModel
# TODO copied file, review
class ServiceModel(BaseModel):
service_id: str
status: str
port: PortType
name: str
proto: str
ip_int: str
n_filters: int
edited_packets: int
blocked_packets: int
class RenameForm(BaseModel):
name:str
class PyFilterModel(BaseModel):
filter_id: int
name: str
blocked_packets: int
edited_packets: int
active: bool
class ServiceAddForm(BaseModel):
name: str
port: PortType
proto: str
ip_int: str
class ServiceAddResponse(BaseModel):
status:str
service_id: str|None = None
app = APIRouter()
db = SQLite('db/nft-pyfilters.db', {
'services': {
'service_id': 'VARCHAR(100) PRIMARY KEY',
'status': 'VARCHAR(100) NOT NULL',
'port': 'INT NOT NULL CHECK(port > 0 and port < 65536)',
'name': 'VARCHAR(100) NOT NULL UNIQUE',
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "http"))',
'ip_int': 'VARCHAR(100) NOT NULL',
},
'pyfilter': {
'filter_id': 'INTEGER PRIMARY KEY',
'name': 'VARCHAR(100) NOT NULL',
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
'edited_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
'service_id': 'VARCHAR(100) NOT NULL',
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1)) DEFAULT 1',
'FOREIGN KEY (service_id)':'REFERENCES services (service_id)',
},
'QUERY':[
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (port, ip_int, proto);",
"CREATE UNIQUE INDEX IF NOT EXISTS unique_pyfilter_service ON pyfilter (name, service_id);"
]
})
async def refresh_frontend(additional:list[str]=[]):
await socketio_emit(["nfproxy"]+additional)
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()
try:
await firewall.init()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
async def startup():
db.init()
try:
await firewall.init()
except Exception as e:
print("WARNING cannot start firewall:", e)
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('/services', response_model=list[ServiceModel])
async def get_service_list():
"""Get the list of existent firegex services"""
return db.query("""
SELECT
s.service_id service_id,
s.status status,
s.port port,
s.name name,
s.proto proto,
s.ip_int ip_int,
COUNT(f.filter_id) n_filters,
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
COALESCE(SUM(f.edited_packets),0) edited_packets
FROM services s LEFT JOIN pyfilter f ON s.service_id = f.service_id
GROUP BY s.service_id;
""")
@app.get('/services/{service_id}', response_model=ServiceModel)
async def get_service_by_id(service_id: str):
"""Get info about a specific service using his id"""
res = db.query("""
SELECT
s.service_id service_id,
s.status status,
s.port port,
s.name name,
s.proto proto,
s.ip_int ip_int,
COUNT(f.filter_id) n_filters,
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
COALESCE(SUM(f.edited_packets),0) edited_packets
FROM services s LEFT JOIN pyfilter f ON s.service_id = f.service_id
WHERE s.service_id = ? GROUP BY s.service_id;
""", service_id)
if len(res) == 0:
raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.post('/services/{service_id}/stop', response_model=StatusMessageModel)
async def service_stop(service_id: str):
"""Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend()
return {'status': 'ok'}
@app.post('/services/{service_id}/start', response_model=StatusMessageModel)
async def service_start(service_id: str):
"""Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend()
return {'status': 'ok'}
@app.delete('/services/{service_id}', response_model=StatusMessageModel)
async def service_delete(service_id: str):
"""Request the deletion of a specific service"""
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
db.query('DELETE FROM pyfilter WHERE service_id = ?;', service_id)
await firewall.remove(service_id)
await refresh_frontend()
return {'status': 'ok'}
@app.put('/services/{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:
raise HTTPException(status_code=400, detail="The name cannot be empty!")
try:
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="This name is already used")
await refresh_frontend()
return {'status': 'ok'}
@app.get('/services/{service_id}/pyfilters', response_model=list[PyFilterModel])
async def get_service_pyfilter_list(service_id: str):
"""Get the list of the pyfilters of a service"""
if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id):
raise HTTPException(status_code=400, detail="This service does not exists!")
return db.query("""
SELECT
filter_id, name, blocked_packets, edited_packets, active
FROM pyfilter WHERE service_id = ?;
""", service_id)
@app.get('/pyfilters/{filter_id}', response_model=PyFilterModel)
async def get_pyfilter_by_id(filter_id: int):
"""Get pyfilter info using his id"""
res = db.query("""
SELECT
filter_id, name, blocked_packets, edited_packets, active
FROM pyfilter WHERE filter_id = ?;
""", filter_id)
if len(res) == 0:
raise HTTPException(status_code=400, detail="This filter does not exists!")
return res[0]
@app.delete('/pyfilters/{filter_id}', response_model=StatusMessageModel)
async def pyfilter_delete(filter_id: int):
"""Delete a pyfilter using his id"""
res = db.query('SELECT * FROM pyfilter WHERE filter_id = ?;', filter_id)
if len(res) != 0:
db.query('DELETE FROM pyfilter WHERE filter_id = ?;', filter_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
@app.post('/pyfilters/{filter_id}/enable', response_model=StatusMessageModel)
async def pyfilter_enable(filter_id: int):
"""Request the enabling of a pyfilter"""
res = db.query('SELECT * FROM pyfilter WHERE filter_id = ?;', filter_id)
if len(res) != 0:
db.query('UPDATE pyfilter SET active=1 WHERE filter_id = ?;', filter_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
@app.post('/pyfilters/{filter_id}/disable', response_model=StatusMessageModel)
async def pyfilter_disable(filter_id: int):
"""Request the deactivation of a pyfilter"""
res = db.query('SELECT * FROM pyfilter WHERE filter_id = ?;', filter_id)
if len(res) != 0:
db.query('UPDATE pyfilter SET active=0 WHERE filter_id = ?;', filter_id)
await firewall.get(res[0]["service_id"]).update_filters()
await refresh_frontend()
return {'status': 'ok'}
@app.post('/services', response_model=ServiceAddResponse)
async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
try:
form.ip_int = ip_parse(form.ip_int)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid address")
if form.proto not in ["tcp", "http"]:
raise HTTPException(status_code=400, detail="Invalid protocol")
srv_id = None
try:
srv_id = gen_service_id()
db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int)
except sqlite3.IntegrityError:
raise HTTPException(status_code=400, detail="This type of service already exists")
await firewall.reload()
await refresh_frontend()
return {'status': 'ok', 'service_id': srv_id}
#TODO check all the APIs and add
# 1. API to change the python filter file
# 2. a socketio mechanism to lock the previous feature

View File

@@ -134,7 +134,7 @@ async def get_service_list():
GROUP BY s.service_id;
""")
@app.get('/service/{service_id}', response_model=ServiceModel)
@app.get('/services/{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("""
@@ -154,21 +154,21 @@ async def get_service_by_id(service_id: str):
raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
@app.post('/services/{service_id}/stop', response_model=StatusMessageModel)
async def service_stop(service_id: str):
"""Request the stop of a specific service"""
await firewall.get(service_id).next(STATUS.STOP)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
@app.post('/services/{service_id}/start', response_model=StatusMessageModel)
async def service_start(service_id: str):
"""Request the start of a specific service"""
await firewall.get(service_id).next(STATUS.ACTIVE)
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
@app.delete('/services/{service_id}', 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)
@@ -177,7 +177,7 @@ async def service_delete(service_id: str):
await refresh_frontend()
return {'status': 'ok'}
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
@app.put('/services/{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)
@@ -190,7 +190,7 @@ async def service_rename(service_id: str, form: RenameForm):
await refresh_frontend()
return {'status': 'ok'}
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
@app.get('/services/{service_id}/regexes', response_model=list[RegexModel])
async def get_service_regexe_list(service_id: str):
"""Get the list of the regexes of a service"""
if not db.query("SELECT 1 FROM services s WHERE s.service_id = ?;", service_id):
@@ -202,7 +202,7 @@ async def get_service_regexe_list(service_id: str):
FROM regexes WHERE service_id = ?;
""", service_id)
@app.get('/regex/{regex_id}', response_model=RegexModel)
@app.get('/regexes/{regex_id}', response_model=RegexModel)
async def get_regex_by_id(regex_id: int):
"""Get regex info using his id"""
res = db.query("""
@@ -215,7 +215,7 @@ async def get_regex_by_id(regex_id: int):
raise HTTPException(status_code=400, detail="This regex does not exists!")
return res[0]
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
@app.delete('/regexes/{regex_id}', response_model=StatusMessageModel)
async def regex_delete(regex_id: int):
"""Delete a regex using his id"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
@@ -226,7 +226,7 @@ async def regex_delete(regex_id: int):
return {'status': 'ok'}
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
@app.post('/regexes/{regex_id}/enable', response_model=StatusMessageModel)
async def regex_enable(regex_id: int):
"""Request the enabling of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
@@ -236,7 +236,7 @@ async def regex_enable(regex_id: int):
await refresh_frontend()
return {'status': 'ok'}
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
@app.post('/regexes/{regex_id}/disable', response_model=StatusMessageModel)
async def regex_disable(regex_id: int):
"""Request the deactivation of a regex"""
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
@@ -246,7 +246,7 @@ async def regex_disable(regex_id: int):
await refresh_frontend()
return {'status': 'ok'}
@app.post('/regexes/add', response_model=StatusMessageModel)
@app.post('/regexes', response_model=StatusMessageModel)
async def add_new_regex(form: RegexAddForm):
"""Add a new regex"""
try:
@@ -263,7 +263,7 @@ async def add_new_regex(form: RegexAddForm):
await refresh_frontend()
return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddResponse)
@app.post('/services', response_model=ServiceAddResponse)
async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
try:
@@ -299,7 +299,8 @@ async def metrics():
FROM regexes r LEFT JOIN services s ON s.service_id = r.service_id;
""")
metrics = []
sanitize = lambda s : s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
def sanitize(s):
return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
for stat in stats:
props = f'service_name="{sanitize(stat["name"])}",regex="{sanitize(b64decode(stat["regex"]).decode())}",mode="{stat["mode"]}",is_case_sensitive="{stat["is_case_sensitive"]}"'
metrics.append(f'firegex_blocked_packets{{{props}}} {stat["blocked_packets"]}')

View File

@@ -92,7 +92,7 @@ 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)
@app.get('/services/{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)
@@ -100,21 +100,21 @@ async def get_service_by_id(service_id: str):
raise HTTPException(status_code=400, detail="This service does not exists!")
return res[0]
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
@app.post('/services/{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)
@app.post('/services/{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)
@app.delete('/services/{service_id}', 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)
@@ -122,7 +122,7 @@ async def service_delete(service_id: str):
await refresh_frontend()
return {'status': 'ok'}
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
@app.put('/services/{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)
@@ -139,7 +139,7 @@ class ChangeDestination(BaseModel):
ip_dst: str
proxy_port: PortType
@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
@app.put('/services/{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"""
@@ -162,7 +162,7 @@ async def service_change_destination(service_id: str, form: ChangeDestination):
await refresh_frontend()
return {'status': 'ok'}
@app.post('/services/add', response_model=ServiceAddResponse)
@app.post('/services', response_model=ServiceAddResponse)
async def add_new_service(form: ServiceAddForm):
"""Add a new service"""
try:

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query"
import { ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
import { getapi, postapi, putapi } from "../../js/utils"
export enum Protocol {
TCP = "tcp",
@@ -79,15 +79,15 @@ export const firewall = {
return await getapi("firewall/settings") as FirewallSettings;
},
setsettings: async(data:FirewallSettings) => {
return await postapi("firewall/settings/set", data) as ServerResponse;
return await putapi("firewall/settings", data) as ServerResponse;
},
enable: async() => {
return await getapi("firewall/enable") as ServerResponse;
return await postapi("firewall/enable") as ServerResponse;
},
disable: async() => {
return await getapi("firewall/disable") as ServerResponse;
return await postapi("firewall/disable") as ServerResponse;
},
ruleset: async (data:RuleAddForm) => {
return await postapi("firewall/rules/set", data) as ServerResponseListed;
return await postapi("firewall/rules", data) as ServerResponseListed;
}
}

View File

@@ -1,5 +1,5 @@
import { RegexFilter, ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
import { deleteapi, getapi, postapi, putapi } from "../../js/utils"
import { RegexAddForm } from "../../js/models"
import { useQuery, useQueryClient } from "@tanstack/react-query"
@@ -40,44 +40,44 @@ export const nfregex = {
return await getapi("nfregex/services") as Service[];
},
serviceinfo: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}`) as Service;
return await getapi(`nfregex/services/${service_id}`) as Service;
},
regexdelete: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/delete`) as ServerResponse;
const { status } = await deleteapi(`nfregex/regexes/${regex_id}`) as ServerResponse;
return status === "ok"?undefined:status
},
regexenable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/enable`) as ServerResponse;
const { status } = await postapi(`nfregex/regexes/${regex_id}/enable`) as ServerResponse;
return status === "ok"?undefined:status
},
regexdisable: async (regex_id:number) => {
const { status } = await getapi(`nfregex/regex/${regex_id}/disable`) as ServerResponse;
const { status } = await postapi(`nfregex/regexes/${regex_id}/disable`) as ServerResponse;
return status === "ok"?undefined:status
},
servicestart: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/start`) as ServerResponse;
const { status } = await postapi(`nfregex/services/${service_id}/start`) as ServerResponse;
return status === "ok"?undefined:status
},
servicerename: async (service_id:string, name: string) => {
const { status } = await postapi(`nfregex/service/${service_id}/rename`,{ name }) as ServerResponse;
const { status } = await putapi(`nfregex/services/${service_id}/rename`,{ name }) as ServerResponse;
return status === "ok"?undefined:status
},
servicestop: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/stop`) as ServerResponse;
const { status } = await postapi(`nfregex/services/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
},
servicesadd: async (data:ServiceAddForm) => {
return await postapi("nfregex/services/add",data) as ServiceAddResponse;
return await postapi("nfregex/services",data) as ServiceAddResponse;
},
servicedelete: async (service_id:string) => {
const { status } = await getapi(`nfregex/service/${service_id}/delete`) as ServerResponse;
const { status } = await deleteapi(`nfregex/services/${service_id}`) as ServerResponse;
return status === "ok"?undefined:status
},
regexesadd: async (data:RegexAddForm) => {
const { status } = await postapi("nfregex/regexes/add",data) as ServerResponse;
const { status } = await postapi("nfregex/regexes",data) as ServerResponse;
return status === "ok"?undefined:status
},
serviceregexes: async (service_id:string) => {
return await getapi(`nfregex/service/${service_id}/regexes`) as RegexFilter[];
return await getapi(`nfregex/services/${service_id}/regexes`) as RegexFilter[];
}
}

View File

@@ -29,24 +29,6 @@ function ServiceRow({ service }:{ service:Service }) {
validate:{ proxy_port: (value) => (value > 0 && value < 65536)? null : "Invalid proxy port" }
})
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)
@@ -119,21 +101,7 @@ function ServiceRow({ service }:{ service:Service }) {
<Space h="sm" />
<Badge color="blue" radius="sm" size="md" variant="filled">
<Box 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: (10+form.values.proxy_port.toString().length*6.2) +"px"
}}
className="firegex__porthijack__servicerow__portInput"
onBlur={(e)=>{onChangeProxyPort({proxy_port:parseInt(e.target.value)})}}
ref={portInputRef}
{...form.getInputProps("proxy_port")}
/>
</form>
TO {service.ip_dst} : service.proxy_port
</Box>
</Badge>
</Box>

View File

@@ -1,5 +1,5 @@
import { ServerResponse } from "../../js/models"
import { getapi, postapi } from "../../js/utils"
import { deleteapi, getapi, postapi, putapi } from "../../js/utils"
import { useQuery } from "@tanstack/react-query"
export type GeneralStats = {
@@ -37,28 +37,28 @@ export const porthijack = {
return await getapi("porthijack/services") as Service[];
},
serviceinfo: async (service_id:string) => {
return await getapi(`porthijack/service/${service_id}`) as Service;
return await getapi(`porthijack/services/${service_id}`) as Service;
},
servicestart: async (service_id:string) => {
const { status } = await getapi(`porthijack/service/${service_id}/start`) as ServerResponse;
const { status } = await postapi(`porthijack/services/${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;
const { status } = await putapi(`porthijack/services/${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;
const { status } = await postapi(`porthijack/services/${service_id}/stop`) as ServerResponse;
return status === "ok"?undefined:status
},
servicesadd: async (data:ServiceAddForm) => {
return await postapi("porthijack/services/add",data) as ServiceAddResponse;
return await postapi("porthijack/services",data) as ServiceAddResponse;
},
servicedelete: async (service_id:string) => {
const { status } = await getapi(`porthijack/service/${service_id}/delete`) as ServerResponse;
const { status } = await deleteapi(`porthijack/services/${service_id}`) 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;
return await putapi(`porthijack/services/${service_id}/change-destination`, {proxy_port, ip_dst}) as ServerResponse;
}
}

View File

@@ -22,26 +22,6 @@ export const queryClient = new QueryClient({ defaultOptions: { queries: {
staleTime: Infinity
} }})
export async function getapi(path:string):Promise<any>{
return await new Promise((resolve, reject) => {
fetch(`${IS_DEV?`http://${DEV_IP_BACKEND}`:""}/api/${path}`,{
credentials: "same-origin",
headers: { "Authorization" : "Bearer " + window.localStorage.getItem("access_token")}
}).then(res => {
if(res.status === 401) window.location.reload()
if(!res.ok){
const errorDefault = res.statusText
return res.json().then( res => reject(getErrorMessageFromServerResponse(res, errorDefault)) ).catch( _err => reject(errorDefault))
}
res.json().then( res => resolve(res) ).catch( err => reject(err))
})
.catch(err => {
reject(err)
})
});
}
export function getErrorMessage(e: any) {
let error = "Unknown error";
if(typeof e == "string") return e
@@ -56,7 +36,6 @@ export function getErrorMessage(e: any) {
return error;
}
export function getErrorMessageFromServerResponse(e: any, def:string = "Unknown error") {
if (e.status){
return e.status
@@ -74,17 +53,17 @@ export function getErrorMessageFromServerResponse(e: any, def:string = "Unknown
}
export async function postapi(path:string,data:any,is_form:boolean=false):Promise<any>{
export async function genericapi(method:string,path:string,data:any = undefined, is_form:boolean=false):Promise<any>{
return await new Promise((resolve, reject) => {
fetch(`${IS_DEV?`http://${DEV_IP_BACKEND}`:""}/api/${path}`, {
method: 'POST',
method: method,
credentials: "same-origin",
cache: 'no-cache',
headers: {
'Content-Type': is_form ? 'application/x-www-form-urlencoded' : 'application/json',
...(data?{'Content-Type': is_form ? 'application/x-www-form-urlencoded' : 'application/json'}:{}),
"Authorization" : "Bearer " + window.localStorage.getItem("access_token")
},
body: is_form ? (new URLSearchParams(data)).toString() : JSON.stringify(data)
body: data? (is_form ? (new URLSearchParams(data)).toString() : JSON.stringify(data)) : undefined
}).then(res => {
if(res.status === 401) window.location.reload()
if(res.status === 406) resolve({status:"Wrong Password"})
@@ -100,6 +79,22 @@ export async function postapi(path:string,data:any,is_form:boolean=false):Promis
});
}
export async function getapi(path:string):Promise<any>{
return await genericapi("GET",path)
}
export async function postapi(path:string,data:any=undefined,is_form:boolean=false):Promise<any>{
return await genericapi("POST",path,data,is_form)
}
export async function deleteapi(path:string):Promise<any>{
return await genericapi("DELETE",path)
}
export async function putapi(path:string,data:any):Promise<any>{
return await genericapi("PUT",path,data)
}
export function getMainPath(){
const paths = window.location.pathname.split("/")
if (paths.length > 1) return paths[1]

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import sys

View File

@@ -19,6 +19,12 @@ class BearerSession():
headers["Content-Type"] = "application/x-www-form-urlencoded"
return self.s.post(endpoint, json=json, data=data, headers=headers)
def delete(self, endpoint, json={}):
return self.s.delete(endpoint, json=json, headers=self.headers)
def put(self, endpoint, json={}):
return self.s.put(endpoint, json=json, headers=self.headers)
def get(self, endpoint, json={}):
return self.s.get(endpoint, json=json, headers=self.headers)
@@ -72,62 +78,57 @@ class FiregexAPI:
def reset(self, delete: bool):
self.s.post(f"{self.address}api/reset", json={"delete":delete})
#Netfilter regex
def nf_get_stats(self):
req = self.s.get(f"{self.address}api/nfregex/stats")
return req.json()
def nf_get_services(self):
req = self.s.get(f"{self.address}api/nfregex/services")
return req.json()
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/services/{service_id}")
return req.json()
def nf_stop_service(self,service_id: str):
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/stop")
req = self.s.post(f"{self.address}api/nfregex/services/{service_id}/stop")
return verify(req)
def nf_start_service(self,service_id: str):
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/start")
req = self.s.post(f"{self.address}api/nfregex/services/{service_id}/start")
return verify(req)
def nf_delete_service(self,service_id: str):
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/delete")
req = self.s.delete(f"{self.address}api/nfregex/services/{service_id}")
return verify(req)
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.put(f"{self.address}api/nfregex/services/{service_id}/rename" , json={"name":newname})
return verify(req)
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/services/{service_id}/regexes")
return req.json()
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/regexes/{regex_id}")
return req.json()
def nf_delete_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/delete")
req = self.s.delete(f"{self.address}api/nfregex/regexes/{regex_id}")
return verify(req)
def nf_enable_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/enable")
req = self.s.post(f"{self.address}api/nfregex/regexes/{regex_id}/enable")
return verify(req)
def nf_disable_regex(self,regex_id: str):
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
req = self.s.post(f"{self.address}api/nfregex/regexes/{regex_id}/disable")
return verify(req)
def nf_add_regex(self, service_id: str, regex: str, mode: str, active: bool, is_case_sensitive: bool):
req = self.s.post(f"{self.address}api/nfregex/regexes/add",
req = self.s.post(f"{self.address}api/nfregex/regexes",
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_case_sensitive": is_case_sensitive})
return verify(req)
def nf_add_service(self, name: str, port: int, proto: str, ip_int: str):
req = self.s.post(f"{self.address}api/nfregex/services/add" ,
req = self.s.post(f"{self.address}api/nfregex/services" ,
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int})
return req.json()["service_id"] if verify(req) else False
@@ -137,30 +138,30 @@ class FiregexAPI:
return req.json()
def ph_get_service(self,service_id: str):
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}")
req = self.s.get(f"{self.address}api/porthijack/services/{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")
req = self.s.post(f"{self.address}api/porthijack/services/{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")
req = self.s.post(f"{self.address}api/porthijack/services/{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")
req = self.s.delete(f"{self.address}api/porthijack/services/{service_id}")
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})
req = self.s.put(f"{self.address}api/porthijack/services/{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})
req = self.s.put(f"{self.address}api/porthijack/services/{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" ,
req = self.s.post(f"{self.address}api/porthijack/services" ,
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