add: filtering table of firewall + InterfaceSelector frontend fixes and improves
This commit is contained in:
15
Dockerfile
15
Dockerfile
@@ -8,20 +8,19 @@ FROM --platform=$BUILDPLATFORM oven/bun AS frontend
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD ./frontend/package.json .
|
ADD ./frontend/package.json .
|
||||||
ADD ./frontend/bun.lockb .
|
ADD ./frontend/bun.lockb .
|
||||||
RUN bun install
|
RUN bun i
|
||||||
COPY ./frontend/ .
|
COPY ./frontend/ .
|
||||||
RUN bun run build
|
RUN bun run build
|
||||||
|
|
||||||
|
|
||||||
#Building main conteiner
|
#Building main conteiner
|
||||||
FROM --platform=$TARGETARCH debian:stable-slim AS base
|
FROM --platform=$TARGETARCH debian:stable-slim AS base
|
||||||
RUN apt-get update -qq && apt-get upgrade -qq
|
RUN apt-get update -qq && apt-get upgrade -qq && \
|
||||||
RUN apt-get install -qq python3-pip build-essential
|
apt-get install -qq python3-pip build-essential \
|
||||||
RUN apt-get install -qq git libpcre2-dev libnetfilter-queue-dev
|
git libpcre2-dev libnetfilter-queue-dev libssl-dev \
|
||||||
RUN apt-get install -qq libssl-dev libnfnetlink-dev libmnl-dev libcap2-bin
|
libnfnetlink-dev libmnl-dev libcap2-bin make cmake \
|
||||||
RUN apt-get install -qq make cmake nftables libboost-all-dev autoconf
|
nftables libboost-all-dev autoconf automake cargo \
|
||||||
RUN apt-get install -qq automake cargo libffi-dev libvectorscan-dev libtins-dev
|
libffi-dev libvectorscan-dev libtins-dev python3-nftables
|
||||||
RUN apt-get install -qq python3-nftables
|
|
||||||
|
|
||||||
WORKDIR /tmp/
|
WORKDIR /tmp/
|
||||||
RUN git clone --single-branch --branch release https://github.com/jpcre2/jpcre2
|
RUN git clone --single-branch --branch release https://github.com/jpcre2/jpcre2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import uvicorn, secrets, utils
|
import uvicorn, secrets, utils
|
||||||
import os, asyncio
|
import os, asyncio, logging
|
||||||
from fastapi import FastAPI, HTTPException, Depends, APIRouter
|
from fastapi import FastAPI, HTTPException, Depends, APIRouter, Request
|
||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
from jose import jwt
|
from jose import jwt
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
@@ -34,7 +34,14 @@ app = FastAPI(debug=DEBUG, redoc_url=None, lifespan=lifespan)
|
|||||||
utils.socketio = SocketManager(app, "/sock", socketio_path="")
|
utils.socketio = SocketManager(app, "/sock", socketio_path="")
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def APP_STATUS(): return "init" if db.get("password") is None else "run"
|
def APP_STATUS(): return "init" if db.get("password") is None else "run"
|
||||||
def JWT_SECRET(): return db.get("secret")
|
def JWT_SECRET(): return db.get("secret")
|
||||||
@@ -132,7 +139,10 @@ async def startup_main():
|
|||||||
db.init()
|
db.init()
|
||||||
if os.getenv("HEX_SET_PSW"):
|
if os.getenv("HEX_SET_PSW"):
|
||||||
set_psw(bytes.fromhex(os.getenv("HEX_SET_PSW")).decode())
|
set_psw(bytes.fromhex(os.getenv("HEX_SET_PSW")).decode())
|
||||||
|
try:
|
||||||
sysctl.set()
|
sysctl.set()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error setting sysctls: {e}")
|
||||||
await startup()
|
await startup()
|
||||||
if not JWT_SECRET(): db.put("secret", secrets.token_hex(32))
|
if not JWT_SECRET(): db.put("secret", secrets.token_hex(32))
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
@@ -149,7 +159,10 @@ async def reset_firegex(form: ResetRequest):
|
|||||||
db.delete()
|
db.delete()
|
||||||
db.init()
|
db.init()
|
||||||
db.put("secret", secrets.token_hex(32))
|
db.put("secret", secrets.token_hex(32))
|
||||||
|
try:
|
||||||
sysctl.set()
|
sysctl.set()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error setting sysctls: {e}")
|
||||||
await reset(form)
|
await reset(form)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from utils import PortType
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
class Rule:
|
class Rule:
|
||||||
def __init__(self, proto: str, src:str, dst:str, port_src_from:str, port_dst_from:str, port_src_to:str, port_dst_to:str, action:str, mode:str, **other):
|
def __init__(self, proto: str, src:str, dst:str, port_src_from:str, port_dst_from:str, port_src_to:str, port_dst_to:str, action:str, mode:str, table:str, **_other):
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
@@ -15,6 +15,7 @@ class Rule:
|
|||||||
self.input_mode = mode == "in"
|
self.input_mode = mode == "in"
|
||||||
self.output_mode = mode == "out"
|
self.output_mode = mode == "out"
|
||||||
self.forward_mode = mode == "forward"
|
self.forward_mode = mode == "forward"
|
||||||
|
self.table = table
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, var: dict):
|
def from_dict(cls, var: dict):
|
||||||
@@ -32,6 +33,10 @@ class Mode(str, Enum):
|
|||||||
OUT = "out",
|
OUT = "out",
|
||||||
FORWARD = "forward"
|
FORWARD = "forward"
|
||||||
|
|
||||||
|
class Table(str, Enum):
|
||||||
|
FILTER = "filter"
|
||||||
|
MANGLE = "mangle"
|
||||||
|
|
||||||
class Action(str, Enum):
|
class Action(str, Enum):
|
||||||
ACCEPT = "accept",
|
ACCEPT = "accept",
|
||||||
DROP = "drop",
|
DROP = "drop",
|
||||||
@@ -41,6 +46,7 @@ class RuleModel(BaseModel):
|
|||||||
active: bool
|
active: bool
|
||||||
name: str
|
name: str
|
||||||
proto: Protocol
|
proto: Protocol
|
||||||
|
table: Table
|
||||||
src: str
|
src: str
|
||||||
dst: str
|
dst: str
|
||||||
port_src_from: PortType
|
port_src_from: PortType
|
||||||
|
|||||||
@@ -7,12 +7,16 @@ class FiregexTables(NFTableManager):
|
|||||||
rules_chain_out = "firegex_firewall_rules_out"
|
rules_chain_out = "firegex_firewall_rules_out"
|
||||||
rules_chain_fwd = "firegex_firewall_rules_fwd"
|
rules_chain_fwd = "firegex_firewall_rules_fwd"
|
||||||
filter_table = "filter"
|
filter_table = "filter"
|
||||||
|
mangle_table = "mangle"
|
||||||
|
|
||||||
def init_comands(self, policy:str=Action.ACCEPT, opt: FirewallSettings|None = None):
|
def init_comands(self, policy:str=Action.ACCEPT, opt: FirewallSettings|None = None):
|
||||||
rules = [
|
rules = [
|
||||||
{"add":{"table":{"name":self.filter_table,"family":"ip"}}},
|
{"add":{"table":{"name":self.filter_table,"family":"ip"}}},
|
||||||
{"add":{"table":{"name":self.filter_table,"family":"ip6"}}},
|
{"add":{"table":{"name":self.filter_table,"family":"ip6"}}},
|
||||||
|
|
||||||
|
{"add":{"table":{"name":self.mangle_table,"family":"ip"}}},
|
||||||
|
{"add":{"table":{"name":self.mangle_table,"family":"ip6"}}},
|
||||||
|
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table, "name":"INPUT","type":"filter","hook":"input","prio":0,"policy":policy}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table, "name":"INPUT","type":"filter","hook":"input","prio":0,"policy":policy}}},
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"INPUT","type":"filter","hook":"input","prio":0,"policy":policy}}},
|
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"INPUT","type":"filter","hook":"input","prio":0,"policy":policy}}},
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":"FORWARD","type":"filter","hook":"forward","prio":0,"policy":policy}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":"FORWARD","type":"filter","hook":"forward","prio":0,"policy":policy}}},
|
||||||
@@ -20,12 +24,22 @@ class FiregexTables(NFTableManager):
|
|||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":"OUTPUT","type":"filter","hook":"output","prio":0,"policy":Action.ACCEPT}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":"OUTPUT","type":"filter","hook":"output","prio":0,"policy":Action.ACCEPT}}},
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"OUTPUT","type":"filter","hook":"output","prio":0,"policy":Action.ACCEPT}}},
|
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"OUTPUT","type":"filter","hook":"output","prio":0,"policy":Action.ACCEPT}}},
|
||||||
|
|
||||||
|
{"add":{"chain":{"family":"ip","table":self.mangle_table, "name":"PREROUTING","type":"filter","hook":"prerouting","prio":-150,"policy":Action.ACCEPT}}},
|
||||||
|
{"add":{"chain":{"family":"ip6","table":self.mangle_table,"name":"PREROUTING","type":"filter","hook":"prerouting","prio":-150,"policy":Action.ACCEPT}}},
|
||||||
|
{"add":{"chain":{"family":"ip","table":self.mangle_table, "name":"POSTROUTING","type":"filter","hook":"postrouting","prio":-150,"policy":Action.ACCEPT}}},
|
||||||
|
{"add":{"chain":{"family":"ip6","table":self.mangle_table,"name":"POSTROUTING","type":"filter","hook":"postrouting","prio":-150,"policy":Action.ACCEPT}}},
|
||||||
|
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_in}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_in}}},
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_in}}},
|
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_in}}},
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_out}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_out}}},
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_out}}},
|
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_out}}},
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_fwd}}},
|
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":self.rules_chain_fwd}}},
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_fwd}}},
|
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":self.rules_chain_fwd}}},
|
||||||
|
|
||||||
|
{"add":{"chain":{"family":"ip","table":self.mangle_table,"name":self.rules_chain_in}}},
|
||||||
|
{"add":{"chain":{"family":"ip6","table":self.mangle_table,"name":self.rules_chain_in}}},
|
||||||
|
{"add":{"chain":{"family":"ip","table":self.mangle_table,"name":self.rules_chain_out}}},
|
||||||
|
{"add":{"chain":{"family":"ip6","table":self.mangle_table,"name":self.rules_chain_out}}},
|
||||||
]
|
]
|
||||||
if opt is None: return rules
|
if opt is None: return rules
|
||||||
|
|
||||||
@@ -158,39 +172,46 @@ class FiregexTables(NFTableManager):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__(self.init_comands(),[
|
super().__init__(self.init_comands(),[
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table, "name":"INPUT","type":"filter","hook":"input","prio":0,"policy":Action.ACCEPT}}},
|
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"INPUT","type":"filter","hook":"input","prio":0,"policy":Action.ACCEPT}}},
|
|
||||||
{"add":{"chain":{"family":"ip","table":self.filter_table,"name":"FORWARD","type":"filter","hook":"forward","prio":0,"policy":Action.ACCEPT}}},
|
|
||||||
{"add":{"chain":{"family":"ip6","table":self.filter_table,"name":"FORWARD","type":"filter","hook":"forward","prio":0,"policy":Action.ACCEPT}}},
|
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_in}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_in}}},
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_out}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_out}}},
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_fwd}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip", "name":self.rules_chain_fwd}}},
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_in}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_in}}},
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_out}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_out}}},
|
||||||
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_fwd}}},
|
{"flush":{"chain":{"table":self.filter_table,"family":"ip6", "name":self.rules_chain_fwd}}},
|
||||||
|
|
||||||
|
{"flush":{"chain":{"table":self.mangle_table,"family":"ip", "name":self.rules_chain_in}}},
|
||||||
|
{"flush":{"chain":{"table":self.mangle_table,"family":"ip", "name":self.rules_chain_out}}},
|
||||||
|
{"flush":{"chain":{"table":self.mangle_table,"family":"ip6", "name":self.rules_chain_in}}},
|
||||||
|
{"flush":{"chain":{"table":self.mangle_table,"family":"ip6", "name":self.rules_chain_out}}}
|
||||||
])
|
])
|
||||||
|
|
||||||
def chain_to_firegex(self, chain:str):
|
def chain_to_firegex(self, chain:str, table:str):
|
||||||
|
if table == self.filter_table:
|
||||||
match chain:
|
match chain:
|
||||||
case "INPUT": return self.rules_chain_in
|
case "INPUT": return self.rules_chain_in
|
||||||
case "OUTPUT": return self.rules_chain_out
|
case "OUTPUT": return self.rules_chain_out
|
||||||
case "FORWARD": return self.rules_chain_fwd
|
case "FORWARD": return self.rules_chain_fwd
|
||||||
|
elif table == self.mangle_table:
|
||||||
|
match chain:
|
||||||
|
case "PREROUTING": return self.rules_chain_in
|
||||||
|
case "POSTROUTING": return self.rules_chain_out
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def insert_firegex_chains(self):
|
def insert_firegex_chains(self):
|
||||||
rules:list[dict] = list(self.list_rules(tables=[self.filter_table], chains=["INPUT", "OUTPUT", "FORWARD"]))
|
rules:list[dict] = list(self.list_rules(tables=[self.filter_table, self.mangle_table], chains=["INPUT", "OUTPUT", "FORWARD", "PREROUTING", "POSTROUTING"]))
|
||||||
|
for table in [self.filter_table, self.mangle_table]:
|
||||||
for family in ["ip", "ip6"]:
|
for family in ["ip", "ip6"]:
|
||||||
for chain in ["INPUT", "OUTPUT", "FORWARD"]:
|
for chain in ["INPUT", "OUTPUT", "FORWARD"] if table == self.filter_table else ["PREROUTING", "POSTROUTING"]:
|
||||||
found = False
|
found = False
|
||||||
rule_to_add = [{ "jump": { "target": self.chain_to_firegex(chain) }}]
|
rule_to_add = [{ "jump": { "target": self.chain_to_firegex(chain, table) }}]
|
||||||
for r in rules:
|
for r in rules:
|
||||||
if r.get("family") == family and r.get("table") == "filter" and r.get("chain") == chain and r.get("expr") == rule_to_add:
|
if r.get("family") == family and r.get("table") == table and r.get("chain") == chain and r.get("expr") == rule_to_add:
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
if found: continue
|
if found: continue
|
||||||
yield { "add":{ "rule": {
|
yield { "add":{ "rule": {
|
||||||
"family": family,
|
"family": family,
|
||||||
"table": self.filter_table,
|
"table": table,
|
||||||
"chain": chain,
|
"chain": chain,
|
||||||
"expr": rule_to_add
|
"expr": rule_to_add
|
||||||
}}}
|
}}}
|
||||||
@@ -209,7 +230,8 @@ class FiregexTables(NFTableManager):
|
|||||||
port_src_to=65535,
|
port_src_to=65535,
|
||||||
port_dst_to=65535,
|
port_dst_to=65535,
|
||||||
action=Action.REJECT,
|
action=Action.REJECT,
|
||||||
mode=Mode.IN
|
mode=Mode.IN,
|
||||||
|
table=Table.FILTER
|
||||||
))
|
))
|
||||||
|
|
||||||
rules = self.init_comands(policy, opt) + list(self.insert_firegex_chains()) + self.get_rules(*srvs)
|
rules = self.init_comands(policy, opt) + list(self.insert_firegex_chains()) + self.get_rules(*srvs)
|
||||||
@@ -261,7 +283,7 @@ class FiregexTables(NFTableManager):
|
|||||||
for fam in families:
|
for fam in families:
|
||||||
rules.append({ "add":{ "rule": {
|
rules.append({ "add":{ "rule": {
|
||||||
"family": fam,
|
"family": fam,
|
||||||
"table": self.filter_table,
|
"table": srv.table,
|
||||||
"chain": self.rules_chain_out if srv.output_mode else self.rules_chain_in if srv.input_mode else self.rules_chain_fwd,
|
"chain": self.rules_chain_out if srv.output_mode else self.rules_chain_in if srv.input_mode else self.rules_chain_fwd,
|
||||||
"expr": ip_filters + port_filters + end_rules
|
"expr": ip_filters + port_filters + end_rules
|
||||||
}}})
|
}}})
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ db = SQLite('db/firewall-rules.db', {
|
|||||||
'rules': {
|
'rules': {
|
||||||
'rule_id': 'INT PRIMARY KEY CHECK (rule_id >= 0)',
|
'rule_id': 'INT PRIMARY KEY CHECK (rule_id >= 0)',
|
||||||
'mode': 'VARCHAR(10) NOT NULL CHECK (mode IN ("in", "out", "forward"))',
|
'mode': 'VARCHAR(10) NOT NULL CHECK (mode IN ("in", "out", "forward"))',
|
||||||
|
'`table`': 'VARCHAR(10) NOT NULL CHECK (`table` IN ("filter", "mangle", "raw"))',
|
||||||
'name': 'VARCHAR(100) NOT NULL',
|
'name': 'VARCHAR(100) NOT NULL',
|
||||||
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
||||||
'proto': 'VARCHAR(10) NOT NULL CHECK (proto IN ("tcp", "udp", "both", "any"))',
|
'proto': 'VARCHAR(10) NOT NULL CHECK (proto IN ("tcp", "udp", "both", "any"))',
|
||||||
@@ -81,7 +82,7 @@ async def get_rule_list():
|
|||||||
"""Get the list of existent firegex rules"""
|
"""Get the list of existent firegex rules"""
|
||||||
return {
|
return {
|
||||||
"policy": firewall.policy,
|
"policy": firewall.policy,
|
||||||
"rules": db.query("SELECT active, name, proto, src, dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode FROM rules ORDER BY rule_id;"),
|
"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;"),
|
||||||
"enabled": firewall.enabled
|
"enabled": firewall.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +100,9 @@ async def disable_firewall():
|
|||||||
|
|
||||||
def parse_and_check_rule(rule:RuleModel):
|
def parse_and_check_rule(rule:RuleModel):
|
||||||
|
|
||||||
|
if rule.table == Table.MANGLE and rule.mode == Mode.FORWARD:
|
||||||
|
raise HTTPException(status_code=400, detail="Mangle table does not support forward mode")
|
||||||
|
|
||||||
is_src_ip = is_dst_ip = True
|
is_src_ip = is_dst_ip = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -137,14 +141,14 @@ async def add_new_service(form: RuleFormAdd):
|
|||||||
src, dst,
|
src, dst,
|
||||||
port_src_from, port_dst_from,
|
port_src_from, port_dst_from,
|
||||||
port_src_to, port_dst_to,
|
port_src_to, port_dst_to,
|
||||||
action, mode
|
action, mode, `table`
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,?, ?)""",
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ,?, ?, ?)""",
|
||||||
rid, ele.active, ele.name,
|
rid, ele.active, ele.name,
|
||||||
ele.proto,
|
ele.proto,
|
||||||
ele.src, ele.dst,
|
ele.src, ele.dst,
|
||||||
ele.port_src_from, ele.port_dst_from,
|
ele.port_src_from, ele.port_dst_from,
|
||||||
ele.port_src_to, ele.port_dst_to,
|
ele.port_src_to, ele.port_dst_to,
|
||||||
ele.action, ele.mode
|
ele.action, ele.mode, ele.table
|
||||||
) for rid, ele in enumerate(rules)]
|
) for rid, ele in enumerate(rules)]
|
||||||
)
|
)
|
||||||
firewall.policy = form.policy.value
|
firewall.policy = form.policy.value
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ socketio:SocketManager = None
|
|||||||
|
|
||||||
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
ROUTERS_DIR = os.path.join(ROOT_DIR,"routers")
|
ROUTERS_DIR = os.path.join(ROOT_DIR,"routers")
|
||||||
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
|
ON_DOCKER = "DOCKER" in sys.argv
|
||||||
DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG"
|
DEBUG = "DEBUG" in sys.argv
|
||||||
FIREGEX_PORT = int(os.getenv("PORT","4444"))
|
FIREGEX_PORT = int(os.getenv("PORT","4444"))
|
||||||
JWT_ALGORITHM: str = "HS256"
|
JWT_ALGORITHM: str = "HS256"
|
||||||
API_VERSION = "2.2.0"
|
API_VERSION = "2.2.0"
|
||||||
@@ -44,8 +44,9 @@ class SysctlManager:
|
|||||||
for name in ctl_table.keys():
|
for name in ctl_table.keys():
|
||||||
self.old_table[name] = read_sysctl(name)
|
self.old_table[name] = read_sysctl(name)
|
||||||
|
|
||||||
def write_table(self, table):
|
def write_table(self, table) -> bool:
|
||||||
for name, value in table.items():
|
for name, value in table.items():
|
||||||
|
if read_sysctl(name) != value:
|
||||||
write_sysctl(name, value)
|
write_sysctl(name, value)
|
||||||
|
|
||||||
def set(self):
|
def set(self):
|
||||||
@@ -124,7 +125,6 @@ class NFTableManager(Singleton):
|
|||||||
|
|
||||||
def cmd(self, *cmds):
|
def cmd(self, *cmds):
|
||||||
code, out, err = self.raw_cmd(*cmds)
|
code, out, err = self.raw_cmd(*cmds)
|
||||||
|
|
||||||
if code == 0: return out
|
if code == 0: return out
|
||||||
else: raise Exception(err)
|
else: raise Exception(err)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
|
|
||||||
import os, httpx
|
import os, httpx
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from fastapi import APIRouter, WebSocket
|
from fastapi import APIRouter
|
||||||
import asyncio
|
|
||||||
from starlette.responses import StreamingResponse
|
from starlette.responses import StreamingResponse
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from utils import DEBUG, ON_DOCKER, ROUTERS_DIR, list_files, run_func
|
from utils import DEBUG, ON_DOCKER, ROUTERS_DIR, list_files, run_func
|
||||||
from utils.models import ResetRequest
|
from utils.models import ResetRequest
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
|
|
||||||
REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/"
|
REACT_BUILD_DIR: str = "../frontend/build/" if not ON_DOCKER else "frontend/"
|
||||||
REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html")
|
REACT_HTML_PATH: str = os.path.join(REACT_BUILD_DIR,"index.html")
|
||||||
@@ -26,15 +24,6 @@ async def react_deploy(path):
|
|||||||
return FileResponse(file_request)
|
return FileResponse(file_request)
|
||||||
|
|
||||||
def frontend_deploy(app):
|
def frontend_deploy(app):
|
||||||
if DEBUG:
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/{full_path:path}", include_in_schema=False)
|
@app.get("/{full_path:path}", include_in_schema=False)
|
||||||
async def catch_all(full_path:str):
|
async def catch_all(full_path:str):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
import { SegmentedControl, SegmentedControlProps } from "@mantine/core";
|
||||||
import { RuleMode } from "./utils";
|
import { RuleMode, Table } from "./utils";
|
||||||
|
import { table } from "console";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const ModeSelector = (props:Omit<SegmentedControlProps, "data">) => (
|
export const ModeSelector = (props:Omit<SegmentedControlProps, "data"> & { table: Table }) => {
|
||||||
<SegmentedControl
|
const isFilterTable = props.table == Table.FILTER
|
||||||
|
return <SegmentedControl
|
||||||
data={[
|
data={[
|
||||||
{
|
{
|
||||||
value: RuleMode.IN,
|
value: RuleMode.IN,
|
||||||
label: 'IN',
|
label: isFilterTable?'IN':'PREROUTING',
|
||||||
},
|
},
|
||||||
{
|
...(isFilterTable?[{
|
||||||
value: RuleMode.FORWARD,
|
value: RuleMode.FORWARD,
|
||||||
label: 'FWD',
|
label: 'FWD',
|
||||||
},
|
}]:[]),
|
||||||
{
|
{
|
||||||
value: RuleMode.OUT,
|
value: RuleMode.OUT,
|
||||||
label: 'OUT',
|
label: isFilterTable?'OUT':'POSTROUTING',
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
size={props.size?props.size:"xs"}
|
size={props.size?props.size:"xs"}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
}
|
||||||
@@ -21,6 +21,11 @@ export enum RuleMode {
|
|||||||
FORWARD = "forward"
|
FORWARD = "forward"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Table {
|
||||||
|
MANGLE = "mangle",
|
||||||
|
FILTER = "filter",
|
||||||
|
}
|
||||||
|
|
||||||
export type Rule = {
|
export type Rule = {
|
||||||
active: boolean
|
active: boolean
|
||||||
name:string,
|
name:string,
|
||||||
@@ -33,6 +38,7 @@ export type Rule = {
|
|||||||
port_dst_to: number,
|
port_dst_to: number,
|
||||||
action: ActionType,
|
action: ActionType,
|
||||||
mode: RuleMode,
|
mode: RuleMode,
|
||||||
|
table: Table
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RuleInfo = {
|
export type RuleInfo = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Combobox, TextInput, useCombobox } from "@mantine/core";
|
import { Combobox, TextInput, useCombobox } from "@mantine/core";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ipInterfacesQuery } from "../js/utils";
|
import { ipInterfacesQuery } from "../js/utils";
|
||||||
|
|
||||||
interface ItemProps{
|
interface ItemProps{
|
||||||
@@ -38,7 +38,7 @@ export const InterfaceInput = ({ initialCustomInterfaces, includeInterfaceNames,
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = [...customIpInterfaces, ...interfaces]
|
const data = [...customIpInterfaces.filter(v => !interfaces.map(v => v.value).includes(v.value)), ...interfaces]
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
@@ -52,12 +52,17 @@ export const InterfaceInput = ({ initialCustomInterfaces, includeInterfaceNames,
|
|||||||
return a.value.localeCompare(b.value)
|
return a.value.localeCompare(b.value)
|
||||||
});
|
});
|
||||||
|
|
||||||
const options = filteredOptions.map((item) => (
|
const options = filteredOptions.sort( (a, b) => a.value == selectedValue? -1 : b.value == selectedValue? 1: a.value.localeCompare(b.value) ).map((item) => (
|
||||||
<Combobox.Option value={item.value} key={item.value}>
|
<Combobox.Option value={item.value} key={item.value}>
|
||||||
( <b>{item.netint}</b> ) -{">"} <b>{item.value}</b>
|
( <b>{item.value == selectedValue ? "SELECTED" : item.netint}</b> ) -{">"} <b>{item.value}</b>
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedValue(data.find((item) => item.value === defaultValue)?.value??null)
|
||||||
|
setSearch(defaultValue??'')
|
||||||
|
}, [])
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Combobox
|
<Combobox
|
||||||
store={combobox}
|
store={combobox}
|
||||||
@@ -81,9 +86,8 @@ export const InterfaceInput = ({ initialCustomInterfaces, includeInterfaceNames,
|
|||||||
<Combobox.Target>
|
<Combobox.Target>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={{width:"100%"}}
|
style={{width:"100%"}}
|
||||||
defaultValue={defaultValue}
|
|
||||||
rightSection={<Combobox.Chevron />}
|
rightSection={<Combobox.Chevron />}
|
||||||
value={value??(defaultValue?undefined:search)}
|
value={value??search}
|
||||||
placeholder="10.1.1.1"
|
placeholder="10.1.1.1"
|
||||||
rightSectionPointerEvents="none"
|
rightSectionPointerEvents="none"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@@ -107,7 +111,7 @@ export const InterfaceInput = ({ initialCustomInterfaces, includeInterfaceNames,
|
|||||||
</Combobox.Target>
|
</Combobox.Target>
|
||||||
|
|
||||||
<Combobox.Dropdown>
|
<Combobox.Dropdown>
|
||||||
<Combobox.Options mah={100} style={{ overflowY: 'auto' }}>
|
<Combobox.Options mah={200} style={{ overflowY: 'auto' }}>
|
||||||
{options}
|
{options}
|
||||||
{(exactOptionMatch==null) && search.trim().length > 0 && (
|
{(exactOptionMatch==null) && search.trim().length > 0 && (
|
||||||
<Combobox.Option value="$create">+ Use this: {search}</Combobox.Option>
|
<Combobox.Option value="$create">+ Use this: {search}</Combobox.Option>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const regex_ipv4 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.
|
|||||||
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
||||||
export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?$"
|
export const regex_port = "^([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?$"
|
||||||
export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$"
|
export const regex_range_port = "^(([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(-([1-9]|[1-9][0-9]{1,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?)?)?$"
|
||||||
export const DEV_IP_BACKEND = "127.0.0.1:4444"
|
export const DEV_IP_BACKEND = "198.19.249.69:4444"
|
||||||
|
|
||||||
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
export const queryClient = new QueryClient({ defaultOptions: { queries: {
|
||||||
staleTime: Infinity
|
staleTime: Infinity
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { ActionIcon, Badge, Box, Button, Divider, LoadingOverlay, Space, Switch, TextInput, Title, Tooltip, useMantineTheme } from "@mantine/core"
|
import { ActionIcon, Badge, Box, Divider, FloatingIndicator, LoadingOverlay, Space, Switch, Table, Tabs, TextInput, Title, Tooltip, useMantineTheme } from "@mantine/core"
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { BsPlusLg, BsTrashFill } from "react-icons/bs"
|
import { BsPlusLg, BsTrashFill } from "react-icons/bs"
|
||||||
import { rem } from '@mantine/core';
|
import { rem } from '@mantine/core';
|
||||||
import { ActionType, Protocol, Rule, RuleMode, firewall, firewallRulesQuery } from "../../components/Firewall/utils";
|
import { ActionType, Protocol, Rule, RuleMode, Table as NFTables, firewall, firewallRulesQuery } from "../../components/Firewall/utils";
|
||||||
import { errorNotify, getErrorMessage, isMediumScreen, makeid, okNotify } from "../../js/utils";
|
import { errorNotify, getErrorMessage, isMediumScreen, makeid, okNotify } from "../../js/utils";
|
||||||
import { useListState, useMediaQuery } from '@mantine/hooks';
|
import { useListState, useMediaQuery } from '@mantine/hooks';
|
||||||
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd';
|
||||||
@@ -62,6 +62,8 @@ export const Firewall = () => {
|
|||||||
const {rule_id, ...rest} = v
|
const {rule_id, ...rest} = v
|
||||||
return rest
|
return rest
|
||||||
})) || rules.data?.policy != currentPolicy
|
})) || rules.data?.policy != currentPolicy
|
||||||
|
const [selectedTab, setSelectedTab] = useState<NFTables>(NFTables.FILTER)
|
||||||
|
|
||||||
|
|
||||||
const enableFirewall = () => {
|
const enableFirewall = () => {
|
||||||
if (valuesChanged){
|
if (valuesChanged){
|
||||||
@@ -122,7 +124,8 @@ export const Firewall = () => {
|
|||||||
port_src_to: 65535,
|
port_src_to: 65535,
|
||||||
port_dst_to: 8080,
|
port_dst_to: 8080,
|
||||||
action: ActionType.ACCEPT,
|
action: ActionType.ACCEPT,
|
||||||
mode: RuleMode.IN
|
mode: RuleMode.IN,
|
||||||
|
table: selectedTab
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +148,7 @@ export const Firewall = () => {
|
|||||||
|
|
||||||
|
|
||||||
const items = state.map((item, index) => (
|
const items = state.map((item, index) => (
|
||||||
<Draggable key={item.rule_id} index={index} draggableId={item.rule_id}>
|
item.table == selectedTab && <Draggable key={item.rule_id} index={index} draggableId={item.rule_id}>
|
||||||
{(provided, snapshot) => {
|
{(provided, snapshot) => {
|
||||||
const customInt = [
|
const customInt = [
|
||||||
{ value: "0.0.0.0/0", netint: "ANY IPv4", label: "0.0.0.0/0" },
|
{ value: "0.0.0.0/0", netint: "ANY IPv4", label: "0.0.0.0/0" },
|
||||||
@@ -313,6 +316,7 @@ export const Firewall = () => {
|
|||||||
value={item.mode}
|
value={item.mode}
|
||||||
onChange={(value)=>{item.mode = value as RuleMode;updateMe()}}
|
onChange={(value)=>{item.mode = value as RuleMode;updateMe()}}
|
||||||
style={{width:"100%"}}
|
style={{width:"100%"}}
|
||||||
|
table={item.table}
|
||||||
/>, !isMedium)}
|
/>, !isMedium)}
|
||||||
<Space h="xs" />
|
<Space h="xs" />
|
||||||
{condDiv(<ProtocolSelector
|
{condDiv(<ProtocolSelector
|
||||||
@@ -335,7 +339,7 @@ export const Firewall = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
}}
|
}}
|
||||||
</Draggable>
|
</Draggable>
|
||||||
));
|
)).filter(v => v);
|
||||||
|
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
@@ -389,6 +393,18 @@ export const Firewall = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Space h="md"/>
|
||||||
|
<Tabs variant="pills" value={selectedTab} onChange={(v)=>setSelectedTab(v==NFTables.MANGLE?NFTables.MANGLE:NFTables.FILTER)} style={{ display:"flex", justifyContent:"center", alignItems:"center"}}>
|
||||||
|
<Box mr="md">Filtering Table:</Box>
|
||||||
|
<Tabs.List>
|
||||||
|
<Tabs.Tab value={NFTables.FILTER}>
|
||||||
|
Filter
|
||||||
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab value={NFTables.MANGLE}>
|
||||||
|
Mangle
|
||||||
|
</Tabs.Tab>
|
||||||
|
</Tabs.List>
|
||||||
|
</Tabs>
|
||||||
{items.length > 0?<DragDropContext
|
{items.length > 0?<DragDropContext
|
||||||
onDragEnd={({ destination, source }) =>
|
onDragEnd={({ destination, source }) =>
|
||||||
handlers.reorder({ from: source.index, to: destination?.index || 0 })
|
handlers.reorder({ from: source.index, to: destination?.index || 0 })
|
||||||
|
|||||||
Reference in New Issue
Block a user