2023-09-22 20:46:50 +02:00
import sqlite3
from fastapi import APIRouter , HTTPException
from utils . sqlite import SQLite
2023-09-29 16:10:28 +02:00
from utils import ip_parse , ip_family , socketio_emit
2023-09-22 20:46:50 +02:00
from utils . models import ResetRequest , StatusMessageModel
from modules . firewall . nftables import FiregexTables
from modules . firewall . firewall import FirewallManager
2025-02-02 19:54:42 +01:00
from modules . firewall . models import FirewallSettings , RuleInfo , RuleModel , RuleFormAdd , Mode , Table
2023-09-28 20:45:58 +02:00
2023-09-22 20:46:50 +02:00
db = SQLite ( ' db/firewall-rules.db ' , {
' rules ' : {
' rule_id ' : ' INT PRIMARY KEY CHECK (rule_id >= 0) ' ,
2023-09-28 20:45:58 +02:00
' mode ' : ' VARCHAR(10) NOT NULL CHECK (mode IN ( " in " , " out " , " forward " )) ' ,
2024-10-19 18:39:42 +02:00
' `table` ' : ' VARCHAR(10) NOT NULL CHECK (`table` IN ( " filter " , " mangle " , " raw " )) ' ,
2023-09-22 20:46:50 +02:00
' name ' : ' VARCHAR(100) NOT NULL ' ,
' active ' : ' BOOLEAN NOT NULL CHECK (active IN (0, 1)) ' ,
2023-09-28 20:45:58 +02:00
' proto ' : ' VARCHAR(10) NOT NULL CHECK (proto IN ( " tcp " , " udp " , " both " , " any " )) ' ,
' src ' : ' VARCHAR(100) NOT NULL ' ,
2023-09-22 20:46:50 +02:00
' 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) ' ,
2023-09-28 20:45:58 +02:00
' dst ' : ' VARCHAR(100) NOT NULL ' ,
2023-09-22 20:46:50 +02:00
' 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 ' : [
2023-09-28 20:45:58 +02:00
" CREATE UNIQUE INDEX IF NOT EXISTS unique_rules ON rules (proto, src, dst, port_src_from, port_src_to, port_dst_from, port_dst_to, mode); "
2023-09-22 20:46:50 +02:00
]
} )
2023-09-28 20:45:58 +02:00
app = APIRouter ( )
2023-09-23 00:23:01 +02:00
firewall = FirewallManager ( db )
2023-09-22 20:46:50 +02:00
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 ( ) :
2023-09-26 01:17:09 +02:00
keep_rules = firewall . keep_rules
2023-09-22 20:46:50 +02:00
db . backup ( )
2023-09-26 01:17:09 +02:00
if not keep_rules :
await firewall . close ( )
2023-09-22 20:46:50 +02:00
db . disconnect ( )
db . restore ( )
2023-09-26 01:17:09 +02:00
async def refresh_frontend ( additional : list [ str ] = [ ] ) :
await socketio_emit ( [ " firewall " ] + additional )
2023-09-22 20:46:50 +02:00
async def apply_changes ( ) :
await firewall . reload ( )
await refresh_frontend ( )
return { ' status ' : ' ok ' }
2023-09-26 01:17:09 +02:00
@app.get ( " /settings " , response_model = FirewallSettings )
async def get_settings ( ) :
""" Get the firewall settings """
2023-09-29 16:10:28 +02:00
return firewall . settings
2023-09-26 01:17:09 +02:00
@app.post ( " /settings/set " , response_model = StatusMessageModel )
async def set_settings ( form : FirewallSettings ) :
""" Set the firewall settings """
2023-09-29 16:10:28 +02:00
firewall . settings = form
2023-09-28 20:45:58 +02:00
return await apply_changes ( )
2023-09-26 01:17:09 +02:00
2023-09-24 05:48:54 +02:00
@app.get ( ' /rules ' , response_model = RuleInfo )
2023-09-22 20:46:50 +02:00
async def get_rule_list ( ) :
""" Get the list of existent firegex rules """
2023-09-23 00:23:01 +02:00
return {
2023-09-26 17:24:04 +02:00
" policy " : firewall . policy ,
2024-10-19 18:39:42 +02:00
" rules " : db . query ( " SELECT active, name, proto, src, dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode, `table` FROM rules ORDER BY rule_id; " ) ,
2023-09-26 17:24:04 +02:00
" enabled " : firewall . enabled
2023-09-23 00:23:01 +02:00
}
2023-09-24 05:48:54 +02:00
@app.get ( ' /enable ' , response_model = StatusMessageModel )
async def enable_firewall ( ) :
""" Request enabling the firewall """
2023-09-26 17:24:04 +02:00
firewall . enabled = True
2023-09-24 05:48:54 +02:00
return await apply_changes ( )
@app.get ( ' /disable ' , response_model = StatusMessageModel )
async def disable_firewall ( ) :
""" Request disabling the firewall """
2023-09-26 17:24:04 +02:00
firewall . enabled = False
2023-09-24 05:48:54 +02:00
return await apply_changes ( )
2023-09-22 20:46:50 +02:00
def parse_and_check_rule ( rule : RuleModel ) :
2023-09-24 19:10:32 +02:00
2024-10-19 18:39:42 +02:00
if rule . table == Table . MANGLE and rule . mode == Mode . FORWARD :
raise HTTPException ( status_code = 400 , detail = " Mangle table does not support forward mode " )
2023-09-28 20:45:58 +02:00
is_src_ip = is_dst_ip = True
try :
rule . src = ip_parse ( rule . src )
except ValueError :
is_src_ip = False
try :
rule . dst = ip_parse ( rule . dst )
except ValueError :
is_dst_ip = False
if not is_src_ip and " / " in rule . src : # Slash is not allowed in ip interfaces names
raise HTTPException ( status_code = 400 , detail = " Invalid source address " )
if not is_dst_ip and " / " in rule . dst :
raise HTTPException ( status_code = 400 , detail = " Invalid destination address " )
if is_src_ip and is_dst_ip and ip_family ( rule . dst ) != ip_family ( rule . src ) :
raise HTTPException ( status_code = 400 , detail = " Destination and source addresses must be of the same family " )
2023-09-22 20:46:50 +02:00
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 )
2023-09-25 18:10:12 +02:00
2023-09-22 20:46:50 +02:00
return rule
2023-09-26 21:16:03 +02:00
@app.post ( ' /rules/set ' , response_model = StatusMessageModel )
2023-09-24 05:48:54 +02:00
async def add_new_service ( form : RuleFormAdd ) :
2023-09-22 20:46:50 +02:00
""" Add a new service """
2023-09-23 00:23:01 +02:00
rules = [ parse_and_check_rule ( ele ) for ele in form . rules ]
2023-09-22 20:46:50 +02:00
try :
db . queries ( [ " DELETE FROM rules " ] +
[ ( """
INSERT INTO rules (
rule_id , active , name ,
proto ,
2023-09-28 20:45:58 +02:00
src , dst ,
2023-09-22 20:46:50 +02:00
port_src_from , port_dst_from ,
port_src_to , port_dst_to ,
2024-10-19 18:39:42 +02:00
action , mode , ` table `
) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) """ ,
2023-09-22 20:46:50 +02:00
rid , ele . active , ele . name ,
ele . proto ,
2023-09-28 20:45:58 +02:00
ele . src , ele . dst ,
2023-09-22 20:46:50 +02:00
ele . port_src_from , ele . port_dst_from ,
ele . port_src_to , ele . port_dst_to ,
2024-10-19 18:39:42 +02:00
ele . action , ele . mode , ele . table
2023-09-23 00:23:01 +02:00
) for rid , ele in enumerate ( rules ) ]
2023-09-22 20:46:50 +02:00
)
2023-09-28 20:45:58 +02:00
firewall . policy = form . policy . value
2023-09-22 20:46:50 +02:00
except sqlite3 . IntegrityError :
2023-09-25 18:10:12 +02:00
raise HTTPException ( status_code = 400 , detail = " Error saving the rules: maybe there are duplicated rules " )
2023-09-22 20:46:50 +02:00
return await apply_changes ( )