From 0c972baa9c26be80ece9c1ca5c24b1258ec17606 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Tue, 26 Sep 2023 01:17:09 +0200 Subject: [PATCH] add(todo): additional setting to implement --- backend/app.py | 5 +- backend/modules/firewall/firewall.py | 33 ++++++++++++- backend/modules/regexproxy/utils.py | 6 +-- backend/routers/firewall.py | 32 ++++++++++++- backend/routers/nfregex.py | 5 +- backend/routers/porthijack.py | 5 +- backend/routers/regexproxy.py | 5 +- backend/utils/__init__.py | 5 +- frontend/src/App.tsx | 8 ++-- frontend/src/components/Firewall/utils.ts | 12 +++++ frontend/src/js/utils.tsx | 5 -- frontend/src/pages/Firewall/SettingsModal.tsx | 48 +++++++++++++++++++ frontend/src/pages/Firewall/index.tsx | 16 +++++++ 13 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 frontend/src/pages/Firewall/SettingsModal.tsx diff --git a/backend/app.py b/backend/app.py index fd322d2..3a044b4 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,7 +6,7 @@ from jose import jwt from passlib.context import CryptContext from fastapi_socketio import SocketManager from utils.sqlite import SQLite -from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, refresh_frontend, DEBUG, SysctlManager +from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, socketio_emit, DEBUG, SysctlManager from utils.loader import frontend_deploy, load_routers from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel from fastapi.middleware.cors import CORSMiddleware @@ -41,6 +41,9 @@ def create_access_token(data: dict): encoded_jwt = jwt.encode(to_encode, JWT_SECRET(), algorithm=JWT_ALGORITHM) return encoded_jwt +async def refresh_frontend(additional:list[str]=[]): + await socketio_emit([]+additional) + async def check_login(token: str = Depends(oauth2_scheme)): if not token: return False diff --git a/backend/modules/firewall/firewall.py b/backend/modules/firewall/firewall.py index 576ea83..243ee37 100644 --- a/backend/modules/firewall/firewall.py +++ b/backend/modules/firewall/firewall.py @@ -21,7 +21,38 @@ class FirewallManager: async def reload(self): async with self.lock: if self.db.get("ENABLED", "0") == "1": - nft.set(map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), policy=self.db.get('POLICY', 'accept')) + additional_rules = [] + if self.allow_loopback: + pass #TODO complete rule + if self.allow_established: + pass #TODO complete rule + rules = list(map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), policy=self.db.get('POLICY', 'accept')) + nft.set(additional_rules + rules) else: nft.reset() + + @property + def keep_rules(self): + return self.db.get("keep_rules", "0") == "1" + + @keep_rules.setter + def keep_rules(self, value): + self.db.set("keep_rules", "1" if value else "0") + + @property + def allow_loopback(self): + return self.db.get("allow_loopback", "1") == "1" + + @allow_loopback.setter + def allow_loopback(self, value): + self.db.set("allow_loopback", "1" if value else "0") + + @property + def allow_established(self): + return self.db.get("allow_established", "1") == "1" + + @allow_established.setter + def allow_established(self, value): + self.db.set("allow_established", "1" if value else "0") + diff --git a/backend/modules/regexproxy/utils.py b/backend/modules/regexproxy/utils.py index eae7895..2695b87 100644 --- a/backend/modules/regexproxy/utils.py +++ b/backend/modules/regexproxy/utils.py @@ -3,7 +3,7 @@ from modules.regexproxy.proxy import Filter, Proxy import random, socket, asyncio from base64 import b64decode from utils.sqlite import SQLite -from utils import refresh_frontend +from utils import socketio_emit class STATUS: WAIT = "wait" @@ -130,10 +130,10 @@ class ServiceManager: while True: if check_port_is_open(self.proxy.public_port): self._set_status(to) - await refresh_frontend() + await socketio_emit(["regexproxy"]) await self.proxy.start(in_pause=(to==STATUS.PAUSE)) self._set_status(STATUS.STOP) - await refresh_frontend() + await socketio_emit(["regexproxy"]) return else: await asyncio.sleep(.5) diff --git a/backend/routers/firewall.py b/backend/routers/firewall.py index 3af7b6d..8fd3099 100644 --- a/backend/routers/firewall.py +++ b/backend/routers/firewall.py @@ -2,7 +2,7 @@ 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 import ip_parse, ip_family, socketio_emit, PortType from utils.models import ResetRequest, StatusMessageModel from modules.firewall.nftables import FiregexTables from modules.firewall.firewall import FirewallManager @@ -35,6 +35,11 @@ class RuleAddResponse(BaseModel): class RenameForm(BaseModel): name:str +class FirewallSettings(BaseModel): + keep_rules: bool + allow_loopback: bool + allow_established: bool + app = APIRouter() db = SQLite('db/firewall-rules.db', { @@ -77,16 +82,39 @@ async def startup(): await firewall.init() async def shutdown(): + keep_rules = firewall.keep_rules db.backup() - await firewall.close() + if not keep_rules: + await firewall.close() db.disconnect() db.restore() +async def refresh_frontend(additional:list[str]=[]): + await socketio_emit(["firewall"]+additional) + async def apply_changes(): await firewall.reload() await refresh_frontend() return {'status': 'ok'} + +@app.get("/settings", response_model=FirewallSettings) +async def get_settings(): + """Get the firewall settings""" + return { + "keep_rules": firewall.keep_rules, + "allow_loopback": firewall.allow_loopback, + "allow_established": firewall.allow_established + } + +@app.post("/settings/set", response_model=StatusMessageModel) +async def set_settings(form: FirewallSettings): + """Set the firewall settings""" + firewall.keep_rules = form.keep_rules + firewall.allow_loopback = form.allow_loopback + firewall.allow_established = form.allow_established + return {'status': 'ok'} + @app.get('/rules', response_model=RuleInfo) async def get_rule_list(): """Get the list of existent firegex rules""" diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 8e66e47..9766a42 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -7,7 +7,7 @@ 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, PortType +from utils import ip_parse, refactor_name, socketio_emit, PortType from utils.models import ResetRequest, StatusMessageModel class ServiceModel(BaseModel): @@ -79,6 +79,9 @@ db = SQLite('db/nft-regex.db', { ] }) +async def refresh_frontend(additional:list[str]=[]): + await socketio_emit(["nfregex"]+additional) + async def reset(params: ResetRequest): if not params.delete: db.backup() diff --git a/backend/routers/porthijack.py b/backend/routers/porthijack.py index 3dc5d3b..6bf8603 100644 --- a/backend/routers/porthijack.py +++ b/backend/routers/porthijack.py @@ -4,7 +4,7 @@ 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, PortType +from utils import addr_parse, ip_family, refactor_name, socketio_emit, PortType from utils.models import ResetRequest, StatusMessageModel from modules.porthijack.nftables import FiregexTables from modules.porthijack.firewall import FirewallManager @@ -75,6 +75,9 @@ async def shutdown(): db.disconnect() db.restore() +async def refresh_frontend(additional:list[str]=[]): + await socketio_emit(["porthijack"]+additional) + def gen_service_id(): while True: res = secrets.token_hex(8) diff --git a/backend/routers/regexproxy.py b/backend/routers/regexproxy.py index f5eb2f4..5360592 100644 --- a/backend/routers/regexproxy.py +++ b/backend/routers/regexproxy.py @@ -5,7 +5,7 @@ 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, PortType +from utils import refactor_name, socketio_emit, PortType app = APIRouter() db = SQLite("db/regextcpproxy.db",{ @@ -56,7 +56,8 @@ async def shutdown(): db.disconnect() db.restore() - +async def refresh_frontend(additional:list[str]=[]): + await socketio_emit(["regexproxy"]+additional) class GeneralStatModel(BaseModel): closed:int diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index 644f6b7..c2f142a 100644 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -4,6 +4,7 @@ import os, socket, psutil, sys, nftables from fastapi_socketio import SocketManager from fastapi import Path from typing import Annotated +import json LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) @@ -25,8 +26,8 @@ async def run_func(func, *args, **kwargs): else: return func(*args, **kwargs) -async def refresh_frontend(): - await socketio.emit("update","Refresh") +async def socketio_emit(elements:list[str]): + await socketio.emit("update",elements) def refactor_name(name:str): name = name.strip() diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ced617c..efce8fe 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,7 +5,7 @@ import { ImCross } from 'react-icons/im'; import { Outlet, Route, Routes } from 'react-router-dom'; import MainLayout from './components/MainLayout'; import { PasswordSend, ServerStatusResponse } from './js/models'; -import { DEV_IP_BACKEND, errorNotify, fireUpdateRequest, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils'; +import { DEV_IP_BACKEND, errorNotify, getstatus, HomeRedirector, IS_DEV, login, setpassword } from './js/utils'; import NFRegex from './pages/NFRegex'; import io from 'socket.io-client'; import RegexProxy from './pages/RegexProxy'; @@ -13,6 +13,7 @@ import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails'; import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails'; import PortHijack from './pages/PortHijack'; import { Firewall } from './pages/Firewall'; +import { useQueryClient } from '@tanstack/react-query'; const socket = IS_DEV?io("ws://"+DEV_IP_BACKEND, {transports: ["websocket", "polling"], path:"/sock" }):io({transports: ["websocket", "polling"], path:"/sock"}); @@ -24,6 +25,7 @@ function App() { const [reqError, setReqError] = useState() const [error, setError] = useState() const [loadinBtn, setLoadingBtn] = useState(false); + const queryClient = useQueryClient() const getStatus = () =>{ getstatus().then( res =>{ @@ -39,8 +41,8 @@ function App() { useEffect(()=>{ getStatus() - socket.on("update", () => { - fireUpdateRequest() + socket.on("update", (data) => { + queryClient.invalidateQueries({ queryKey: data }) }) socket.on("connect_error", (err) => { errorNotify("Socket.Io connection failed! ",`Error message: [${err.message}]`) diff --git a/frontend/src/components/Firewall/utils.ts b/frontend/src/components/Firewall/utils.ts index a84dcbb..c2eb4eb 100644 --- a/frontend/src/components/Firewall/utils.ts +++ b/frontend/src/components/Firewall/utils.ts @@ -44,6 +44,12 @@ export type RuleAddForm = { policy: ActionType } +export type FirewallSettings = { + keep_rules: boolean, + allow_loopback: boolean, + allow_established: boolean, +} + export type ServerResponseListed = { status:(ServerResponse & {rule_id:number})[]|string, @@ -56,6 +62,12 @@ export const firewall = { rules: async() => { return await getapi("firewall/rules") as RuleInfo; }, + settings: async() => { + return await getapi("firewall/settings") as FirewallSettings; + }, + setsettings: async(data:FirewallSettings) => { + return await postapi("firewall/settings/set", data) as ServerResponse; + }, enable: async() => { return await getapi("firewall/enable") as ServerResponse; }, diff --git a/frontend/src/js/utils.tsx b/frontend/src/js/utils.tsx index 3bd14e6..5bc7009 100644 --- a/frontend/src/js/utils.tsx +++ b/frontend/src/js/utils.tsx @@ -103,11 +103,6 @@ export function HomeRedirector(){ return } -export function fireUpdateRequest(){ //TODO: change me: specify what to update - queryClient.invalidateQueries({ queryKey: [] }) -} - - export async function resetfiregex(delete_data:boolean = false){ const { status } = await postapi("reset",{delete:delete_data}) as ServerResponse; return (status === "ok"?undefined:status) diff --git a/frontend/src/pages/Firewall/SettingsModal.tsx b/frontend/src/pages/Firewall/SettingsModal.tsx new file mode 100644 index 0000000..2d59f12 --- /dev/null +++ b/frontend/src/pages/Firewall/SettingsModal.tsx @@ -0,0 +1,48 @@ +import { Button, Group, Space, Modal, Switch } from '@mantine/core'; +import { useEffect, useState } from 'react'; +import { errorNotify, okNotify } from '../../js/utils'; +import { FirewallSettings, firewall } from '../../components/Firewall/utils'; + +export function SettingsModal({ opened, onClose }:{ opened:boolean, onClose:()=>void }) { + + const [settings, setSettings] = useState({keep_rules:false, allow_established:true, allow_loopback:true}) + + useEffect(()=>{ + firewall.settings().then( res => { + setSettings(res) + }).catch( err => { + errorNotify("Setting fetch failed!", err.toString()) + onClose() + }) + },[]) + + const [submitLoading, setSubmitLoading] = useState(false) + + const submitRequest = () =>{ + setSubmitLoading(true) + firewall.setsettings(settings).then( () => { + okNotify("Settings updated!", "Settings updated successfully") + setSubmitLoading(false) + onClose() + }).catch( err => { + errorNotify("Settings update failed!", err.toString()) + setSubmitLoading(false) + }) + } + + + return + + setSettings({...settings, keep_rules:v.target.checked})}/> + + setSettings({...settings, allow_loopback:v.target.checked})}/> + + setSettings({...settings, allow_established:v.target.checked})}/> + + + + + + + +} diff --git a/frontend/src/pages/Firewall/index.tsx b/frontend/src/pages/Firewall/index.tsx index 6adc874..d2fa03f 100644 --- a/frontend/src/pages/Firewall/index.tsx +++ b/frontend/src/pages/Firewall/index.tsx @@ -18,6 +18,8 @@ import { ModeSelector } from "../../components/Firewall/ModeSelector"; import { OnOffButton } from "../../components/OnOffButton"; import { LuArrowBigRightDash } from "react-icons/lu" import { ImCheckmark, ImCross } from "react-icons/im"; +import { IoSettingsSharp } from "react-icons/io5"; +import { SettingsModal } from "./SettingsModal"; export const Firewall = () => { @@ -25,6 +27,7 @@ export const Firewall = () => { const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false); + const [tooltipSettingsOpened, setTooltipSettingsOpened] = useState(false); const [currentPolicy, setCurrentPolicy] = useState(ActionType.ACCEPT) const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false) const queryClient = useQueryClient() @@ -32,6 +35,7 @@ export const Firewall = () => { const [state, handlers] = useListState([]); const [enableFwModal, setEnableFwModal] = useState(false) const [applyChangeModal, setApplyChangeModal] = useState(false) + const [settingsModal, setSettingsModal] = useState(false) const theme = useMantineTheme(); const [updateMevalueinternal, internalUpdateme] = useState(false) @@ -383,6 +387,12 @@ export const Firewall = () => { onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + + setSettingsModal(true)} size="lg" radius="md" variant="filled" + onFocus={() => setTooltipSettingsOpened(false)} onBlur={() => setTooltipSettingsOpened(false)} + onMouseEnter={() => setTooltipSettingsOpened(true)} onMouseLeave={() => setTooltipSettingsOpened(false)}> + + setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)} @@ -436,5 +446,11 @@ export const Firewall = () => { opened={applyChangeModal} /> + setSettingsModal(false)} + /> + + } \ No newline at end of file