Added reset button
This commit is contained in:
@@ -11,6 +11,7 @@ from passlib.context import CryptContext
|
|||||||
from fastapi_socketio import SocketManager
|
from fastapi_socketio import SocketManager
|
||||||
from modules import SQLite, FirewallManager
|
from modules import SQLite, FirewallManager
|
||||||
from modules.firewall import STATUS
|
from modules.firewall import STATUS
|
||||||
|
from modules.firegex import FiregexTables
|
||||||
from utils import get_interfaces, ip_parse, refactor_name, gen_service_id
|
from utils import get_interfaces, ip_parse, refactor_name, gen_service_id
|
||||||
|
|
||||||
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
|
ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER"
|
||||||
@@ -69,7 +70,7 @@ async def check_login(token: str = Depends(oauth2_scheme)):
|
|||||||
try:
|
try:
|
||||||
payload = jwt.decode(token, JWT_SECRET(), algorithms=[settings.JWT_ALGORITHM])
|
payload = jwt.decode(token, JWT_SECRET(), algorithms=[settings.JWT_ALGORITHM])
|
||||||
logged_in: bool = payload.get("logged_in")
|
logged_in: bool = payload.get("logged_in")
|
||||||
except JWTError:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return logged_in
|
return logged_in
|
||||||
|
|
||||||
@@ -374,6 +375,27 @@ async def get_ip_interfaces(auth: bool = Depends(is_loggined)):
|
|||||||
"""Get a list of ip and ip6 interfaces"""
|
"""Get a list of ip and ip6 interfaces"""
|
||||||
return get_interfaces()
|
return get_interfaces()
|
||||||
|
|
||||||
|
class ResetRequest(BaseModel):
|
||||||
|
delete:bool
|
||||||
|
|
||||||
|
@app.post('/api/reset', response_model=StatusMessageModel)
|
||||||
|
async def reset_firegex(form: ResetRequest, auth: bool = Depends(is_loggined)):
|
||||||
|
"""Reset firegex nftables rules and optionally all the database"""
|
||||||
|
if not form.delete:
|
||||||
|
db.backup()
|
||||||
|
await firewall.close()
|
||||||
|
FiregexTables().reset()
|
||||||
|
if form.delete:
|
||||||
|
db.delete()
|
||||||
|
db.init()
|
||||||
|
db.put("secret", secrets.token_hex(32))
|
||||||
|
else:
|
||||||
|
db.restore()
|
||||||
|
await firewall.init()
|
||||||
|
await refresh_frontend()
|
||||||
|
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
async def frontend_debug_proxy(path):
|
async def frontend_debug_proxy(path):
|
||||||
httpc = httpx.AsyncClient()
|
httpc = httpx.AsyncClient()
|
||||||
req = httpc.build_request("GET",f"http://127.0.0.1:{os.getenv('F_PORT','3000')}/"+path)
|
req = httpc.build_request("GET",f"http://127.0.0.1:{os.getenv('F_PORT','3000')}/"+path)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class FiregexTables:
|
|||||||
else: raise Exception(err)
|
else: raise Exception(err)
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
|
self.reset()
|
||||||
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
|
code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}})
|
||||||
if code == 0:
|
if code == 0:
|
||||||
self.cmd(
|
self.cmd(
|
||||||
@@ -61,10 +62,13 @@ class FiregexTables:
|
|||||||
"policy":"accept"
|
"policy":"accept"
|
||||||
}}}
|
}}}
|
||||||
)
|
)
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.cmd({"flush":{"table":{"name":"firegex","family":"inet"}}})
|
self.raw_cmd(
|
||||||
|
{"flush":{"table":{"name":"firegex","family":"inet"}}},
|
||||||
|
{"delete":{"table":{"name":"firegex","family":"inet"}}},
|
||||||
|
)
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
return self.cmd({"list": {"ruleset": None}})["nftables"]
|
return self.cmd({"list": {"ruleset": None}})["nftables"]
|
||||||
|
|||||||
@@ -50,18 +50,24 @@ class SQLite():
|
|||||||
self.conn.row_factory = dict_factory
|
self.conn.row_factory = dict_factory
|
||||||
|
|
||||||
def backup(self):
|
def backup(self):
|
||||||
if self.conn:
|
with open(self.db_name, "rb") as f:
|
||||||
with open(self.db_name, "rb") as f:
|
self.__backup = f.read()
|
||||||
self.__backup = f.read()
|
|
||||||
|
|
||||||
def restore(self):
|
def restore(self):
|
||||||
|
were_active = True if self.conn else False
|
||||||
|
self.disconnect()
|
||||||
if self.__backup:
|
if self.__backup:
|
||||||
with open(self.db_name, "wb") as f:
|
with open(self.db_name, "wb") as f:
|
||||||
f.write(self.__backup)
|
f.write(self.__backup)
|
||||||
self.__backup = None
|
self.__backup = None
|
||||||
|
if were_active: self.connect()
|
||||||
|
|
||||||
|
def delete_backup(self):
|
||||||
|
self.__backup = None
|
||||||
|
|
||||||
def disconnect(self) -> None:
|
def disconnect(self) -> None:
|
||||||
if self.conn: self.conn.close()
|
if self.conn: self.conn.close()
|
||||||
|
self.conn = None
|
||||||
|
|
||||||
def create_schema(self, tables = {}) -> None:
|
def create_schema(self, tables = {}) -> None:
|
||||||
if self.conn:
|
if self.conn:
|
||||||
|
|||||||
64
frontend/src/components/Header/ResetModal.tsx
Normal file
64
frontend/src/components/Header/ResetModal.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { Button, Group, Modal, Notification, Space, Switch, Text } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/hooks";
|
||||||
|
import React, { useState } from "react"
|
||||||
|
import { ImCross } from "react-icons/im";
|
||||||
|
import { okNotify, resetfiregex } from "../../js/utils";
|
||||||
|
|
||||||
|
function ResetModal({ opened, onClose }:{ opened: boolean, onClose: () => void }) {
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
delete_data:false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [loadingBtn, setLoadingBtn] = useState(false)
|
||||||
|
const [error, setError] = useState<null|string>(null)
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
setError(null)
|
||||||
|
setLoadingBtn(false)
|
||||||
|
form.reset()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitRequest = async ({ delete_data }:{ delete_data:boolean }) => {
|
||||||
|
setLoadingBtn(true)
|
||||||
|
await resetfiregex(delete_data).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify("Firegex Resetted!","Firegex has been resetted!")
|
||||||
|
setError(null);
|
||||||
|
close()
|
||||||
|
form.reset()
|
||||||
|
}else{
|
||||||
|
setError(res)
|
||||||
|
}
|
||||||
|
}).catch( err => setError(err.toString()))
|
||||||
|
setLoadingBtn(false)
|
||||||
|
}
|
||||||
|
return <Modal size="xl" title="Reset Firegex" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||||
|
<b>Resetting firegex will trigger the reloading of the firewall rules in nftables and the restarting
|
||||||
|
of all services filters</b> (technically the c++ filter processes).<br />
|
||||||
|
This will only cause the stop of the filters for a second and then restore them, during this time the services will continue to be available without interruptions.<br />
|
||||||
|
<b><Text color="red">Enabling the option below you will totaly reset firegex like you started it for the first time.</Text></b>
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<Space h="md" />
|
||||||
|
<Switch
|
||||||
|
label="Delete all data, including the firewall rules"
|
||||||
|
{...form.getInputProps('delete_data', { type: 'checkbox' })}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={loadingBtn} onClick={close} >Cancel</Button>
|
||||||
|
<Button loading={loadingBtn} type="submit" color="red">Reset</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
<Space h="xl" />
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetModal;
|
||||||
62
frontend/src/components/Header/ResetPasswordModal.tsx
Normal file
62
frontend/src/components/Header/ResetPasswordModal.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Button, Group, Modal, Notification, PasswordInput, Space, Switch } from "@mantine/core";
|
||||||
|
import { useForm } from "@mantine/hooks";
|
||||||
|
import React, { useState } from "react"
|
||||||
|
import { ImCross } from "react-icons/im";
|
||||||
|
import { ChangePassword } from "../../js/models";
|
||||||
|
import { changepassword, okNotify } from "../../js/utils";
|
||||||
|
|
||||||
|
function ResetPasswordModal({ opened, onClose }:{ opened: boolean, onClose: () => void }) {
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
password:"",
|
||||||
|
expire:true
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
password: (value) => value !== ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const [loadingBtn, setLoadingBtn] = useState(false)
|
||||||
|
const [error, setError] = useState<null|string>(null)
|
||||||
|
|
||||||
|
const submitRequest = async (values:ChangePassword) => {
|
||||||
|
setLoadingBtn(true)
|
||||||
|
await changepassword(values).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify("Password change done!","The password of the firewall has been changed!")
|
||||||
|
onClose()
|
||||||
|
form.reset()
|
||||||
|
}else{
|
||||||
|
setError(res)
|
||||||
|
}
|
||||||
|
}).catch( err => setError(err.toString()))
|
||||||
|
setLoadingBtn(false)
|
||||||
|
}
|
||||||
|
return <Modal size="xl" title="Change Firewall Password" opened={opened} onClose={onClose} closeOnClickOutside={false} centered>
|
||||||
|
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<Space h="md" />
|
||||||
|
<PasswordInput
|
||||||
|
label="New Password"
|
||||||
|
placeholder="$3cr3t"
|
||||||
|
{...form.getInputProps('password')}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Switch
|
||||||
|
label="Expire the login status to all connections"
|
||||||
|
{...form.getInputProps('expire', { type: 'checkbox' })}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={loadingBtn} type="submit">Change Password</Button>
|
||||||
|
</Group>
|
||||||
|
</form>
|
||||||
|
<Space h="xl" />
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ResetPasswordModal;
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ActionIcon, Badge, Button, Divider, Group, Image, Menu, Modal, Notification, Space, Switch, Tooltip, FloatingTooltip, MediaQuery, PasswordInput } from '@mantine/core';
|
import { ActionIcon, Badge, Divider, Image, Menu, Space, Tooltip, FloatingTooltip, MediaQuery } from '@mantine/core';
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
import { changepassword, errorNotify, eventUpdateName, generalstats, logout, okNotify } from '../../js/utils';
|
import { errorNotify, eventUpdateName, generalstats, logout } from '../../js/utils';
|
||||||
import { ChangePassword, GeneralStats } from '../../js/models';
|
import { GeneralStats } from '../../js/models';
|
||||||
import { BsPlusLg } from "react-icons/bs"
|
import { BsPlusLg } from "react-icons/bs"
|
||||||
import { AiFillHome } from "react-icons/ai"
|
import { AiFillHome } from "react-icons/ai"
|
||||||
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
import AddNewRegex from '../AddNewRegex';
|
import AddNewRegex from '../AddNewRegex';
|
||||||
import AddNewService from '../AddNewService';
|
import AddNewService from '../AddNewService';
|
||||||
import { FaLock } from 'react-icons/fa';
|
import { FaLock } from 'react-icons/fa';
|
||||||
import { ImCross, ImExit } from 'react-icons/im';
|
import { MdOutlineSettingsBackupRestore } from 'react-icons/md';
|
||||||
import { useForm, useWindowEvent } from '@mantine/hooks';
|
import { ImExit } from 'react-icons/im';
|
||||||
|
import { useWindowEvent } from '@mantine/hooks';
|
||||||
|
import ResetPasswordModal from './ResetPasswordModal';
|
||||||
|
import ResetModal from './ResetModal';
|
||||||
|
|
||||||
|
|
||||||
function Header() {
|
function Header() {
|
||||||
@@ -38,37 +41,11 @@ function Header() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const form = useForm({
|
|
||||||
initialValues: {
|
|
||||||
password:"",
|
|
||||||
expire:true
|
|
||||||
},
|
|
||||||
validationRules:{
|
|
||||||
password: (value) => value !== ""
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const [loadingBtn, setLoadingBtn] = useState(false)
|
|
||||||
const [error, setError] = useState<null|string>(null)
|
|
||||||
const [changePasswordModal, setChangePasswordModal] = useState(false);
|
const [changePasswordModal, setChangePasswordModal] = useState(false);
|
||||||
|
const [resetFiregexModal, setResetFiregexModal] = useState(false);
|
||||||
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||||
const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false);
|
const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false);
|
||||||
|
|
||||||
const submitRequest = async (values:ChangePassword) => {
|
|
||||||
setLoadingBtn(true)
|
|
||||||
await changepassword(values).then(res => {
|
|
||||||
if(!res){
|
|
||||||
okNotify("Password change done!","The password of the firewall has been changed!")
|
|
||||||
setChangePasswordModal(false)
|
|
||||||
form.reset()
|
|
||||||
}else{
|
|
||||||
setError(res)
|
|
||||||
}
|
|
||||||
}).catch( err => setError(err.toString()))
|
|
||||||
setLoadingBtn(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const {srv} = useParams()
|
const {srv} = useParams()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -108,8 +85,11 @@ function Header() {
|
|||||||
<Menu>
|
<Menu>
|
||||||
<Menu.Label>Firewall Access</Menu.Label>
|
<Menu.Label>Firewall Access</Menu.Label>
|
||||||
<Menu.Item icon={<ImExit size={14} />} onClick={logout_action}>Logout</Menu.Item>
|
<Menu.Item icon={<ImExit size={14} />} onClick={logout_action}>Logout</Menu.Item>
|
||||||
<Divider />
|
|
||||||
<Menu.Item color="red" icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
|
<Menu.Item color="red" icon={<FaLock size={14} />} onClick={() => setChangePasswordModal(true)}>Change Password</Menu.Item>
|
||||||
|
<Divider />
|
||||||
|
<Menu.Label>Actions</Menu.Label>
|
||||||
|
<Menu.Item color="red" icon={<MdOutlineSettingsBackupRestore size={18} />} onClick={() => setResetFiregexModal(true)}>Reset Firegex</Menu.Item>
|
||||||
|
|
||||||
</Menu>
|
</Menu>
|
||||||
<div style={{marginLeft:"20px"}}></div>
|
<div style={{marginLeft:"20px"}}></div>
|
||||||
{ location.pathname !== "/"?
|
{ location.pathname !== "/"?
|
||||||
@@ -143,31 +123,9 @@ function Header() {
|
|||||||
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
|
<AddNewRegex opened={open} onClose={closeModal} service={srv} />:
|
||||||
<AddNewService opened={open} onClose={closeModal} />
|
<AddNewService opened={open} onClose={closeModal} />
|
||||||
}
|
}
|
||||||
<Modal size="xl" title="Change Firewall Password" opened={changePasswordModal} onClose={()=>setChangePasswordModal(false)} closeOnClickOutside={false} centered>
|
<ResetPasswordModal opened={changePasswordModal} onClose={() => setChangePasswordModal(false)} />
|
||||||
|
<ResetModal opened={resetFiregexModal} onClose={() => setResetFiregexModal(false)} />
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
|
||||||
<Space h="md" />
|
|
||||||
<PasswordInput
|
|
||||||
label="New Password"
|
|
||||||
placeholder="$3cr3t"
|
|
||||||
{...form.getInputProps('password')}
|
|
||||||
/>
|
|
||||||
<Space h="md" />
|
|
||||||
<Switch
|
|
||||||
label="Expire the login status to all connections"
|
|
||||||
{...form.getInputProps('expire', { type: 'checkbox' })}
|
|
||||||
/>
|
|
||||||
<Space h="md" />
|
|
||||||
<Group position="right" mt="md">
|
|
||||||
<Button loading={loadingBtn} type="submit">Change Password</Button>
|
|
||||||
</Group>
|
|
||||||
</form>
|
|
||||||
<Space h="xl" />
|
|
||||||
{error?<>
|
|
||||||
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
|
||||||
Error: {error}
|
|
||||||
</Notification><Space h="md" /></>:null}
|
|
||||||
</Modal>
|
|
||||||
<div style={{marginLeft:"40px"}}></div>
|
<div style={{marginLeft:"40px"}}></div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ export function fireUpdateRequest(){
|
|||||||
window.dispatchEvent(new Event(eventUpdateName))
|
window.dispatchEvent(new Event(eventUpdateName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resetfiregex(delete_data:boolean = false){
|
||||||
|
const { status } = await postapi("reset",{delete:delete_data}) as ServerResponse;
|
||||||
|
return (status === "ok"?undefined:status)
|
||||||
|
}
|
||||||
|
|
||||||
export async function getipinterfaces(){
|
export async function getipinterfaces(){
|
||||||
return await getapi("interfaces") as IpInterface[];
|
return await getapi("interfaces") as IpInterface[];
|
||||||
}
|
}
|
||||||
@@ -87,7 +92,6 @@ export async function setpassword(data:PasswordSend) {
|
|||||||
|
|
||||||
export async function changepassword(data:ChangePassword) {
|
export async function changepassword(data:ChangePassword) {
|
||||||
const { status, access_token } = await postapi("change-password",data) as ServerResponseToken;
|
const { status, access_token } = await postapi("change-password",data) as ServerResponseToken;
|
||||||
console.log(access_token)
|
|
||||||
if (access_token)
|
if (access_token)
|
||||||
window.localStorage.setItem("access_token", access_token);
|
window.localStorage.setItem("access_token", access_token);
|
||||||
return status === "ok"?undefined:status
|
return status === "ok"?undefined:status
|
||||||
|
|||||||
Reference in New Issue
Block a user