add(todo): additional setting to implement

This commit is contained in:
Domingo Dirutigliano
2023-09-26 01:17:09 +02:00
parent a9446d6dc6
commit 0c972baa9c
13 changed files with 164 additions and 21 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<undefined|string>()
const [error, setError] = useState<string|null>()
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}]`)

View File

@@ -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;
},

View File

@@ -103,11 +103,6 @@ export function HomeRedirector(){
return <Navigate to={path} replace/>
}
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)

View File

@@ -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<FirewallSettings>({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 <Modal size="xl" title="Change firewall settings" opened={opened} onClose={onClose} closeOnClickOutside={false} centered>
<Switch label="Keep rules on firegex shutdown" checked={settings.keep_rules} onChange={v => setSettings({...settings, keep_rules:v.target.checked})}/>
<Space h="md" />
<Switch label="Allow loopback to communicate with itself" checked={settings.allow_loopback} onChange={v => setSettings({...settings, allow_loopback:v.target.checked})}/>
<Space h="md" />
<Switch label="Allow established connection (essential to allow opening connection) (Dangerous to disable)" checked={settings.allow_established} onChange={v => setSettings({...settings, allow_established:v.target.checked})}/>
<Space h="md" />
<Group position="right" mt="md">
<Button loading={submitLoading} onClick={submitRequest}>Save Setting</Button>
</Group>
</Modal>
}

View File

@@ -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>(ActionType.ACCEPT)
const [tooltipAddRulOpened, setTooltipAddRulOpened] = useState(false)
const queryClient = useQueryClient()
@@ -32,6 +35,7 @@ export const Firewall = () => {
const [state, handlers] = useListState<Rule & {rule_id:string}>([]);
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)}><TbReload size={18} /></ActionIcon>
</Tooltip>
<Space w="xs" />
<Tooltip label="Settings" position='bottom' color="cyan" opened={tooltipSettingsOpened}>
<ActionIcon color="cyan" onClick={()=>setSettingsModal(true)} size="lg" radius="md" variant="filled"
onFocus={() => setTooltipSettingsOpened(false)} onBlur={() => setTooltipSettingsOpened(false)}
onMouseEnter={() => setTooltipSettingsOpened(true)} onMouseLeave={() => setTooltipSettingsOpened(false)}><IoSettingsSharp size={18} /></ActionIcon>
</Tooltip>
<Space w="xs" />
<Tooltip label="Apply" position='bottom' color="grape" opened={tooltipApplyOpened}>
<ActionIcon color="grape" onClick={applyChanges} size="lg" radius="md" variant="filled"
onFocus={() => setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)}
@@ -436,5 +446,11 @@ export const Firewall = () => {
opened={applyChangeModal}
/>
<SettingsModal
opened={settingsModal}
onClose={()=>setSettingsModal(false)}
/>
</>
}