adding firewall function to firegex!
This commit is contained in:
171
backend/routers/firewall.py
Normal file
171
backend/routers/firewall.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import sqlite3
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from utils.sqlite import SQLite
|
||||
from utils import ip_parse, ip_family, refactor_name, refresh_frontend, PortType
|
||||
from utils.models import ResetRequest, StatusMessageModel
|
||||
from modules.firewall.nftables import FiregexTables
|
||||
from modules.firewall.firewall import FirewallManager
|
||||
|
||||
class RuleModel(BaseModel):
|
||||
active: bool
|
||||
name: str
|
||||
proto: str
|
||||
ip_src: str
|
||||
ip_dst: str
|
||||
port_src_from: PortType
|
||||
port_dst_from: PortType
|
||||
port_src_to: PortType
|
||||
port_dst_to: PortType
|
||||
action: str
|
||||
mode:str
|
||||
|
||||
class RuleAddResponse(BaseModel):
|
||||
status:str|list[dict]
|
||||
|
||||
class RenameForm(BaseModel):
|
||||
name:str
|
||||
|
||||
class GeneralStatModel(BaseModel):
|
||||
rules: int
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
db = SQLite('db/firewall-rules.db', {
|
||||
'rules': {
|
||||
'rule_id': 'INT PRIMARY KEY CHECK (rule_id >= 0)',
|
||||
'mode': 'VARCHAR(1) NOT NULL CHECK (mode IN ("O", "I"))', # O = out, I = in, B = both
|
||||
'name': 'VARCHAR(100) NOT NULL',
|
||||
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
||||
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp", "any"))',
|
||||
'ip_src': 'VARCHAR(100) NOT NULL',
|
||||
'port_src_from': 'INT CHECK(port_src_from > 0 and port_src_from < 65536)',
|
||||
'port_src_to': 'INT CHECK(port_src_to > 0 and port_src_to < 65536 and port_src_from <= port_src_to)',
|
||||
'ip_dst': 'VARCHAR(100) NOT NULL',
|
||||
'port_dst_from': 'INT CHECK(port_dst_from > 0 and port_dst_from < 65536)',
|
||||
'port_dst_to': 'INT CHECK(port_dst_to > 0 and port_dst_to < 65536 and port_dst_from <= port_dst_to)',
|
||||
'action': 'VARCHAR(10) NOT NULL CHECK (action IN ("accept", "drop", "reject"))',
|
||||
},
|
||||
'QUERY':[
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS unique_rules ON rules (proto, ip_src, ip_dst, port_src_from, port_src_to, port_dst_from, port_dst_to, action);"
|
||||
]
|
||||
})
|
||||
|
||||
async def reset(params: ResetRequest):
|
||||
if not params.delete:
|
||||
db.backup()
|
||||
await firewall.close()
|
||||
FiregexTables().reset()
|
||||
if params.delete:
|
||||
db.delete()
|
||||
db.init()
|
||||
else:
|
||||
db.restore()
|
||||
await firewall.init()
|
||||
|
||||
|
||||
async def startup():
|
||||
db.init()
|
||||
await firewall.init()
|
||||
|
||||
async def shutdown():
|
||||
db.backup()
|
||||
await firewall.close()
|
||||
db.disconnect()
|
||||
db.restore()
|
||||
|
||||
async def apply_changes():
|
||||
await firewall.reload()
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
firewall = FirewallManager(db)
|
||||
|
||||
@app.get('/stats', response_model=GeneralStatModel)
|
||||
async def get_general_stats():
|
||||
"""Get firegex general status about rules"""
|
||||
return db.query("SELECT (SELECT COUNT(*) FROM rules) rules")[0]
|
||||
|
||||
@app.get('/rules', response_model=list[RuleModel])
|
||||
async def get_rule_list():
|
||||
"""Get the list of existent firegex rules"""
|
||||
return db.query("SELECT active, name, proto, ip_src, ip_dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode FROM rules ORDER BY rule_id;")
|
||||
|
||||
@app.get('/rule/{rule_id}/disable', response_model=StatusMessageModel)
|
||||
async def service_disable(rule_id: str):
|
||||
"""Request disabling a specific rule"""
|
||||
if len(db.query('SELECT 1 FROM rules WHERE rule_id = ?;', rule_id)) == 0:
|
||||
return {'status': 'Rule not found'}
|
||||
db.query('UPDATE rules SET active = 0 WHERE rule_id = ?;', rule_id)
|
||||
return await apply_changes()
|
||||
|
||||
@app.get('/rule/{rule_id}/enable', response_model=StatusMessageModel)
|
||||
async def service_start(rule_id: str):
|
||||
"""Request the enabling a specific rule"""
|
||||
if len(db.query('SELECT 1 FROM rules WHERE rule_id = ?;', rule_id)) == 0:
|
||||
return {'status': 'Rule not found'}
|
||||
db.query('UPDATE rules SET active = 1 WHERE rule_id = ?;', rule_id)
|
||||
return await apply_changes()
|
||||
|
||||
@app.post('/service/{rule_id}/rename', response_model=StatusMessageModel)
|
||||
async def service_rename(rule_id: str, form: RenameForm):
|
||||
"""Request to change the name of a specific service"""
|
||||
if len(db.query('SELECT 1 FROM rules WHERE rule_id = ?;', rule_id)) == 0:
|
||||
return {'status': 'Rule not found'}
|
||||
form.name = refactor_name(form.name)
|
||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||
try:
|
||||
db.query('UPDATE rules SET name=? WHERE rule_id = ?;', form.name, rule_id)
|
||||
except sqlite3.IntegrityError:
|
||||
return {'status': 'This name is already used'}
|
||||
await refresh_frontend()
|
||||
return {'status': 'ok'}
|
||||
|
||||
def parse_and_check_rule(rule:RuleModel):
|
||||
try:
|
||||
rule.ip_src = ip_parse(rule.ip_src)
|
||||
rule.ip_dst = ip_parse(rule.ip_dst)
|
||||
except ValueError:
|
||||
return {"status":"Invalid address"}
|
||||
|
||||
rule.port_dst_from, rule.port_dst_to = min(rule.port_dst_from, rule.port_dst_to), max(rule.port_dst_from, rule.port_dst_to)
|
||||
rule.port_src_from, rule.port_src_to = min(rule.port_src_from, rule.port_src_to), max(rule.port_src_from, rule.port_src_to)
|
||||
|
||||
if ip_family(rule.ip_dst) != ip_family(rule.ip_src):
|
||||
return {"status":"Destination and source addresses must be of the same family"}
|
||||
if rule.proto not in ["tcp", "udp", "any"]:
|
||||
return {"status":"Invalid protocol"}
|
||||
if rule.action not in ["accept", "drop", "reject"]:
|
||||
return {"status":"Invalid action"}
|
||||
return rule
|
||||
|
||||
|
||||
@app.post('/rules/set', response_model=RuleAddResponse)
|
||||
async def add_new_service(form: list[RuleModel]):
|
||||
"""Add a new service"""
|
||||
form = [parse_and_check_rule(ele) for ele in form]
|
||||
errors = [({"rule":i} | ele) for i, ele in enumerate(form) if isinstance(ele, dict)]
|
||||
if len(errors) > 0:
|
||||
return {'status': errors}
|
||||
try:
|
||||
db.queries(["DELETE FROM rules"]+
|
||||
[("""
|
||||
INSERT INTO rules (
|
||||
rule_id, active, name,
|
||||
proto,
|
||||
ip_src, ip_dst,
|
||||
port_src_from, port_dst_from,
|
||||
port_src_to, port_dst_to,
|
||||
action, mode
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,?, ?)""",
|
||||
rid, ele.active, ele.name,
|
||||
ele.proto,
|
||||
ele.ip_src, ele.ip_dst,
|
||||
ele.port_src_from, ele.port_dst_from,
|
||||
ele.port_src_to, ele.port_dst_to,
|
||||
ele.action, ele.mode
|
||||
) for rid, ele in enumerate(form)]
|
||||
)
|
||||
except sqlite3.IntegrityError:
|
||||
return {'status': 'Error saving the rules: maybe there are duplicated rules'}
|
||||
return await apply_changes()
|
||||
@@ -2,13 +2,12 @@ from base64 import b64decode
|
||||
import re
|
||||
import secrets
|
||||
import sqlite3
|
||||
from typing import List, Union
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from modules.nfregex.nftables import FiregexTables
|
||||
from modules.nfregex.firewall import STATUS, FirewallManager
|
||||
from utils.sqlite import SQLite
|
||||
from utils import ip_parse, refactor_name, refresh_frontend
|
||||
from utils import ip_parse, refactor_name, refresh_frontend, PortType
|
||||
from utils.models import ResetRequest, StatusMessageModel
|
||||
|
||||
class GeneralStatModel(BaseModel):
|
||||
@@ -19,7 +18,7 @@ class GeneralStatModel(BaseModel):
|
||||
class ServiceModel(BaseModel):
|
||||
status: str
|
||||
service_id: str
|
||||
port: int
|
||||
port: PortType
|
||||
name: str
|
||||
proto: str
|
||||
ip_int: str
|
||||
@@ -43,19 +42,19 @@ class RegexAddForm(BaseModel):
|
||||
service_id: str
|
||||
regex: str
|
||||
mode: str
|
||||
active: Union[bool,None]
|
||||
active: bool|None
|
||||
is_blacklist: bool
|
||||
is_case_sensitive: bool
|
||||
|
||||
class ServiceAddForm(BaseModel):
|
||||
name: str
|
||||
port: int
|
||||
port: PortType
|
||||
proto: str
|
||||
ip_int: str
|
||||
|
||||
class ServiceAddResponse(BaseModel):
|
||||
status:str
|
||||
service_id: Union[None,str]
|
||||
service_id: str|None
|
||||
|
||||
app = APIRouter()
|
||||
|
||||
@@ -70,7 +69,7 @@ db = SQLite('db/nft-regex.db', {
|
||||
},
|
||||
'regexes': {
|
||||
'regex': 'TEXT NOT NULL',
|
||||
'mode': 'VARCHAR(1) NOT NULL',
|
||||
'mode': 'VARCHAR(1) NOT NULL CHECK (mode IN ("C", "S", "B"))', # C = to the client, S = to the server, B = both
|
||||
'service_id': 'VARCHAR(100) NOT NULL',
|
||||
'is_blacklist': 'BOOLEAN NOT NULL CHECK (is_blacklist IN (0, 1))',
|
||||
'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0',
|
||||
@@ -127,7 +126,7 @@ async def get_general_stats():
|
||||
(SELECT COUNT(*) FROM services) services
|
||||
""")[0]
|
||||
|
||||
@app.get('/services', response_model=List[ServiceModel])
|
||||
@app.get('/services', response_model=list[ServiceModel])
|
||||
async def get_service_list():
|
||||
"""Get the list of existent firegex services"""
|
||||
return db.query("""
|
||||
@@ -198,7 +197,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('/service/{service_id}/regexes', response_model=list[RegexModel])
|
||||
async def get_service_regexe_list(service_id: str):
|
||||
"""Get the list of the regexes of a service"""
|
||||
return db.query("""
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import secrets
|
||||
import sqlite3
|
||||
from typing import List, Union
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from modules.porthijack.models import Service
|
||||
from utils.sqlite import SQLite
|
||||
from utils import addr_parse, ip_family, refactor_name, refresh_frontend
|
||||
from utils import addr_parse, ip_family, refactor_name, refresh_frontend, PortType
|
||||
from utils.models import ResetRequest, StatusMessageModel
|
||||
from modules.porthijack.nftables import FiregexTables
|
||||
from modules.porthijack.firewall import FirewallManager
|
||||
@@ -13,8 +12,8 @@ from modules.porthijack.firewall import FirewallManager
|
||||
class ServiceModel(BaseModel):
|
||||
service_id: str
|
||||
active: bool
|
||||
public_port: int
|
||||
proxy_port: int
|
||||
public_port: PortType
|
||||
proxy_port: PortType
|
||||
name: str
|
||||
proto: str
|
||||
ip_src: str
|
||||
@@ -25,15 +24,15 @@ class RenameForm(BaseModel):
|
||||
|
||||
class ServiceAddForm(BaseModel):
|
||||
name: str
|
||||
public_port: int
|
||||
proxy_port: int
|
||||
public_port: PortType
|
||||
proxy_port: PortType
|
||||
proto: str
|
||||
ip_src: str
|
||||
ip_dst: str
|
||||
|
||||
class ServiceAddResponse(BaseModel):
|
||||
status:str
|
||||
service_id: Union[None,str]
|
||||
service_id: str|None
|
||||
|
||||
class GeneralStatModel(BaseModel):
|
||||
services: int
|
||||
@@ -96,7 +95,7 @@ async def get_general_stats():
|
||||
(SELECT COUNT(*) FROM services) services
|
||||
""")[0]
|
||||
|
||||
@app.get('/services', response_model=List[ServiceModel])
|
||||
@app.get('/services', response_model=list[ServiceModel])
|
||||
async def get_service_list():
|
||||
"""Get the list of existent firegex services"""
|
||||
return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services;")
|
||||
@@ -144,7 +143,7 @@ async def service_rename(service_id: str, form: RenameForm):
|
||||
|
||||
class ChangeDestination(BaseModel):
|
||||
ip_dst: str
|
||||
proxy_port: int
|
||||
proxy_port: PortType
|
||||
|
||||
@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
|
||||
async def service_change_destination(service_id: str, form: ChangeDestination):
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
from base64 import b64decode
|
||||
import sqlite3, re
|
||||
from typing import List, Union
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from modules.regexproxy.utils import STATUS, ProxyManager, gen_internal_port, gen_service_id
|
||||
from utils.sqlite import SQLite
|
||||
from utils.models import ResetRequest, StatusMessageModel
|
||||
from utils import refactor_name, refresh_frontend
|
||||
from utils import refactor_name, refresh_frontend, PortType
|
||||
|
||||
app = APIRouter()
|
||||
db = SQLite("db/regextcpproxy.db",{
|
||||
@@ -77,13 +76,13 @@ async def get_general_stats():
|
||||
class ServiceModel(BaseModel):
|
||||
id:str
|
||||
status: str
|
||||
public_port: int
|
||||
internal_port: int
|
||||
public_port: PortType
|
||||
internal_port: PortType
|
||||
name: str
|
||||
n_regex: int
|
||||
n_packets: int
|
||||
|
||||
@app.get('/services', response_model=List[ServiceModel])
|
||||
@app.get('/services', response_model=list[ServiceModel])
|
||||
async def get_service_list():
|
||||
"""Get the list of existent firegex services"""
|
||||
return db.query("""
|
||||
@@ -157,8 +156,8 @@ async def regen_service_port(service_id: str):
|
||||
return {'status': 'ok'}
|
||||
|
||||
class ChangePortForm(BaseModel):
|
||||
port: Union[int, None]
|
||||
internalPort: Union[int, None]
|
||||
port: int|None
|
||||
internalPort: int|None
|
||||
|
||||
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
|
||||
async def change_service_ports(service_id: str, change_port:ChangePortForm):
|
||||
@@ -167,7 +166,7 @@ async def change_service_ports(service_id: str, change_port:ChangePortForm):
|
||||
return {'status': 'Invalid Request!'}
|
||||
try:
|
||||
sql_inj = ""
|
||||
query:List[Union[str,int]] = []
|
||||
query:list[str|int] = []
|
||||
if not change_port.port is None:
|
||||
sql_inj+=" public_port = ? "
|
||||
query.append(change_port.port)
|
||||
@@ -194,7 +193,7 @@ class RegexModel(BaseModel):
|
||||
is_case_sensitive:bool
|
||||
active:bool
|
||||
|
||||
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||
@app.get('/service/{service_id}/regexes', response_model=list[RegexModel])
|
||||
async def get_service_regexe_list(service_id: str):
|
||||
"""Get the list of the regexes of a service"""
|
||||
return db.query("""
|
||||
@@ -250,7 +249,7 @@ class RegexAddForm(BaseModel):
|
||||
service_id: str
|
||||
regex: str
|
||||
mode: str
|
||||
active: Union[bool,None]
|
||||
active: bool|None
|
||||
is_blacklist: bool
|
||||
is_case_sensitive: bool
|
||||
|
||||
@@ -272,12 +271,12 @@ async def add_new_regex(form: RegexAddForm):
|
||||
|
||||
class ServiceAddForm(BaseModel):
|
||||
name: str
|
||||
port: int
|
||||
internalPort: Union[int, None]
|
||||
port: PortType
|
||||
internalPort: int|None
|
||||
|
||||
class ServiceAddStatus(BaseModel):
|
||||
status:str
|
||||
id: Union[str,None]
|
||||
id: str|None
|
||||
|
||||
class RenameForm(BaseModel):
|
||||
name:str
|
||||
|
||||
Reference in New Issue
Block a user