adding firewall function to firegex!

This commit is contained in:
Domingo Dirutigliano
2023-09-22 20:46:50 +02:00
parent 4b8b145b68
commit 7fda371dcb
20 changed files with 890 additions and 145 deletions

171
backend/routers/firewall.py Normal file
View 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()

View File

@@ -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("""

View File

@@ -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):

View File

@@ -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