asdas
This commit is contained in:
223
backend/routers/setup.py
Normal file
223
backend/routers/setup.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, UploadFile, File
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import json
|
||||||
|
from typing import List, Optional
|
||||||
|
from utils.models import StatusMessageModel
|
||||||
|
from routers import nfproxy, nfregex, porthijack, firewall
|
||||||
|
|
||||||
|
class ServiceConfig(BaseModel):
|
||||||
|
name: str
|
||||||
|
port: int
|
||||||
|
proto: str
|
||||||
|
ip_int: str
|
||||||
|
fail_open: bool = True
|
||||||
|
|
||||||
|
class PortHijackServiceConfig(BaseModel):
|
||||||
|
name: str
|
||||||
|
public_port: int
|
||||||
|
proxy_port: int
|
||||||
|
proto: str
|
||||||
|
ip_src: str
|
||||||
|
ip_dst: str
|
||||||
|
|
||||||
|
class FirewallRuleConfig(BaseModel):
|
||||||
|
mode: str
|
||||||
|
src: str
|
||||||
|
dst: str
|
||||||
|
in_int: str
|
||||||
|
out_int: str
|
||||||
|
proto: str
|
||||||
|
sport: str
|
||||||
|
dport: str
|
||||||
|
|
||||||
|
class SetupConfig(BaseModel):
|
||||||
|
services: Optional[List[ServiceConfig]] = []
|
||||||
|
porthijack: Optional[List[PortHijackServiceConfig]] = []
|
||||||
|
firewall: Optional[List[FirewallRuleConfig]] = []
|
||||||
|
|
||||||
|
class SetupResponse(BaseModel):
|
||||||
|
status: str
|
||||||
|
services_created: int = 0
|
||||||
|
porthijack_created: int = 0
|
||||||
|
firewall_created: int = 0
|
||||||
|
errors: List[str] = []
|
||||||
|
|
||||||
|
app = APIRouter()
|
||||||
|
|
||||||
|
@app.post("/import", response_model=SetupResponse)
|
||||||
|
async def import_setup(config: SetupConfig):
|
||||||
|
"""
|
||||||
|
Import services and rules from a setup configuration.
|
||||||
|
Creates basic services without filters or regex rules.
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
services_count = 0
|
||||||
|
porthijack_count = 0
|
||||||
|
firewall_count = 0
|
||||||
|
|
||||||
|
# Import Services
|
||||||
|
if config.services:
|
||||||
|
for service_config in config.services:
|
||||||
|
try:
|
||||||
|
# Determine which module to use based on protocol
|
||||||
|
# HTTP -> NFProxy, TCP/UDP -> can use either (prefer NFProxy)
|
||||||
|
if service_config.proto in ["tcp", "http", "udp"]:
|
||||||
|
# Create NFProxy service
|
||||||
|
try:
|
||||||
|
add_form = nfproxy.ServiceAddForm(
|
||||||
|
name=service_config.name,
|
||||||
|
port=service_config.port,
|
||||||
|
proto=service_config.proto,
|
||||||
|
ip_int=service_config.ip_int,
|
||||||
|
fail_open=service_config.fail_open
|
||||||
|
)
|
||||||
|
result = await nfproxy.add_service(add_form)
|
||||||
|
|
||||||
|
if result.status == "ok":
|
||||||
|
services_count += 1
|
||||||
|
else:
|
||||||
|
errors.append(f"Service '{service_config.name}': Failed to create")
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Service '{service_config.name}': {str(e)}")
|
||||||
|
else:
|
||||||
|
errors.append(f"Service '{service_config.name}': Unsupported protocol '{service_config.proto}'")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Service '{service_config.name}': {str(e)}")
|
||||||
|
|
||||||
|
# Import PortHijack services
|
||||||
|
if config.porthijack:
|
||||||
|
for service_config in config.porthijack:
|
||||||
|
try:
|
||||||
|
add_form = porthijack.ServiceAddForm(
|
||||||
|
name=service_config.name,
|
||||||
|
public_port=service_config.public_port,
|
||||||
|
proxy_port=service_config.proxy_port,
|
||||||
|
proto=service_config.proto,
|
||||||
|
ip_src=service_config.ip_src,
|
||||||
|
ip_dst=service_config.ip_dst
|
||||||
|
)
|
||||||
|
result = await porthijack.add_service(add_form)
|
||||||
|
|
||||||
|
if result.status == "ok":
|
||||||
|
porthijack_count += 1
|
||||||
|
else:
|
||||||
|
errors.append(f"PortHijack service '{service_config.name}': Failed to create")
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"PortHijack service '{service_config.name}': {str(e)}")
|
||||||
|
|
||||||
|
# Import Firewall rules
|
||||||
|
if config.firewall:
|
||||||
|
for rule_config in config.firewall:
|
||||||
|
try:
|
||||||
|
rule_form = firewall.RuleFormAdd(
|
||||||
|
mode=rule_config.mode,
|
||||||
|
src=rule_config.src,
|
||||||
|
dst=rule_config.dst,
|
||||||
|
in_int=rule_config.in_int,
|
||||||
|
out_int=rule_config.out_int,
|
||||||
|
proto=rule_config.proto,
|
||||||
|
sport=rule_config.sport,
|
||||||
|
dport=rule_config.dport
|
||||||
|
)
|
||||||
|
await firewall.add_rule(rule_form)
|
||||||
|
firewall_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"Firewall rule: {str(e)}")
|
||||||
|
|
||||||
|
return SetupResponse(
|
||||||
|
status="ok" if len(errors) == 0 else "partial",
|
||||||
|
services_created=services_count,
|
||||||
|
porthijack_created=porthijack_count,
|
||||||
|
firewall_created=firewall_count,
|
||||||
|
errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.post("/import/file")
|
||||||
|
async def import_setup_file(file: UploadFile = File(...)):
|
||||||
|
"""
|
||||||
|
Import services from an uploaded JSON file.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
content = await file.read()
|
||||||
|
config_dict = json.loads(content.decode('utf-8'))
|
||||||
|
config = SetupConfig(**config_dict)
|
||||||
|
return await import_setup(config)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Invalid JSON: {str(e)}")
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f"Error processing file: {str(e)}")
|
||||||
|
|
||||||
|
@app.get("/export")
|
||||||
|
async def export_setup():
|
||||||
|
"""
|
||||||
|
Export all current services and rules as a JSON configuration.
|
||||||
|
Exports only service definitions without filters or regexes.
|
||||||
|
"""
|
||||||
|
config = {
|
||||||
|
"services": [],
|
||||||
|
"porthijack": [],
|
||||||
|
"firewall": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Export NFProxy services
|
||||||
|
try:
|
||||||
|
nfproxy_services = await nfproxy.get_services()
|
||||||
|
for service in nfproxy_services:
|
||||||
|
config["services"].append({
|
||||||
|
"name": service.name,
|
||||||
|
"port": service.port,
|
||||||
|
"proto": service.proto,
|
||||||
|
"ip_int": service.ip_int,
|
||||||
|
"fail_open": service.fail_open
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Export NFRegex services
|
||||||
|
try:
|
||||||
|
nfregex_services = await nfregex.get_services()
|
||||||
|
for service in nfregex_services:
|
||||||
|
config["services"].append({
|
||||||
|
"name": service.name,
|
||||||
|
"port": service.port,
|
||||||
|
"proto": service.proto,
|
||||||
|
"ip_int": service.ip_int,
|
||||||
|
"fail_open": service.fail_open
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Export PortHijack services
|
||||||
|
try:
|
||||||
|
porthijack_services = await porthijack.get_services()
|
||||||
|
for service in porthijack_services:
|
||||||
|
config["porthijack"].append({
|
||||||
|
"name": service.name,
|
||||||
|
"public_port": service.public_port,
|
||||||
|
"proxy_port": service.proxy_port,
|
||||||
|
"proto": service.proto,
|
||||||
|
"ip_src": service.ip_src,
|
||||||
|
"ip_dst": service.ip_dst
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Export Firewall rules
|
||||||
|
try:
|
||||||
|
fw_rules = await firewall.get_rules()
|
||||||
|
for rule in fw_rules:
|
||||||
|
config["firewall"].append({
|
||||||
|
"mode": rule.mode,
|
||||||
|
"src": rule.src,
|
||||||
|
"dst": rule.dst,
|
||||||
|
"in_int": rule.in_int,
|
||||||
|
"out_int": rule.out_int,
|
||||||
|
"proto": rule.proto,
|
||||||
|
"sport": rule.sport,
|
||||||
|
"dport": rule.dport
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return config
|
||||||
278
frontend/src/pages/Setup/index.tsx
Normal file
278
frontend/src/pages/Setup/index.tsx
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import { Box, Button, Code, Divider, FileButton, Group, List, Paper, Space, Stack, Text, Textarea, ThemeIcon, Title } from '@mantine/core';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { FaCheck, FaDownload, FaExclamationTriangle, FaTimes, FaUpload } from 'react-icons/fa';
|
||||||
|
import { MdSettings } from 'react-icons/md';
|
||||||
|
import { getapi, isMediumScreen, postapi } from '../../js/utils';
|
||||||
|
import { errorNotify, successNotify } from '../../js/utils';
|
||||||
|
|
||||||
|
export default function SetupPage() {
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
const [importing, setImporting] = useState(false);
|
||||||
|
const [exporting, setExporting] = useState(false);
|
||||||
|
const [importResult, setImportResult] = useState<any>(null);
|
||||||
|
const [configJson, setConfigJson] = useState('');
|
||||||
|
const isMedium = isMediumScreen();
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
setExporting(true);
|
||||||
|
try {
|
||||||
|
const response = await getapi('/setup/export');
|
||||||
|
const blob = new Blob([JSON.stringify(response, null, 2)], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `firegex-setup-${new Date().toISOString().split('T')[0]}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
successNotify('Configuration exported successfully');
|
||||||
|
} catch (err) {
|
||||||
|
errorNotify('Failed to export configuration', String(err));
|
||||||
|
} finally {
|
||||||
|
setExporting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportFile = async () => {
|
||||||
|
if (!file) {
|
||||||
|
errorNotify('Please select a file first', '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImporting(true);
|
||||||
|
setImportResult(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
const response = await fetch('/api/setup/import/file', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.detail || 'Import failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
setImportResult(result);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
successNotify('Configuration imported successfully');
|
||||||
|
} else {
|
||||||
|
errorNotify('Configuration imported with errors', 'Check the results below');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errorNotify('Failed to import configuration', String(err));
|
||||||
|
} finally {
|
||||||
|
setImporting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImportJson = async () => {
|
||||||
|
if (!configJson.trim()) {
|
||||||
|
errorNotify('Please enter a JSON configuration', '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImporting(true);
|
||||||
|
setImportResult(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(configJson);
|
||||||
|
const result = await postapi('/setup/import', config);
|
||||||
|
setImportResult(result);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
successNotify('Configuration imported successfully');
|
||||||
|
} else {
|
||||||
|
errorNotify('Configuration imported with errors', 'Check the results below');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof SyntaxError) {
|
||||||
|
errorNotify('Invalid JSON format', String(err));
|
||||||
|
} else {
|
||||||
|
errorNotify('Failed to import configuration', String(err));
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setImporting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box px="md" mt="lg">
|
||||||
|
<Title order={1} className="center-flex">
|
||||||
|
<ThemeIcon radius="md" size="lg" variant='filled' color='cyan'>
|
||||||
|
<MdSettings size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Space w="sm" />
|
||||||
|
Setup Import/Export
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" mt="sm">
|
||||||
|
Import or export your Firegex configuration including all services and rules
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Divider my="lg" />
|
||||||
|
|
||||||
|
<Stack gap="xl">
|
||||||
|
{/* Export Section */}
|
||||||
|
<Paper shadow="sm" p="lg" withBorder>
|
||||||
|
<Title order={3} mb="md">Export Configuration</Title>
|
||||||
|
<Text c="dimmed" mb="md">
|
||||||
|
Download all current services and rules as a JSON file
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
leftSection={<FaDownload />}
|
||||||
|
onClick={handleExport}
|
||||||
|
loading={exporting}
|
||||||
|
color="teal"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
Export to JSON
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Import from File Section */}
|
||||||
|
<Paper shadow="sm" p="lg" withBorder>
|
||||||
|
<Title order={3} mb="md">Import from File</Title>
|
||||||
|
<Text c="dimmed" mb="md">
|
||||||
|
Upload a setup.json file to create services and rules
|
||||||
|
</Text>
|
||||||
|
<Group>
|
||||||
|
<FileButton onChange={setFile} accept="application/json">
|
||||||
|
{(props) => (
|
||||||
|
<Button {...props} variant="outline" color="cyan">
|
||||||
|
{file ? file.name : 'Select JSON File'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</FileButton>
|
||||||
|
<Button
|
||||||
|
leftSection={<FaUpload />}
|
||||||
|
onClick={handleImportFile}
|
||||||
|
loading={importing}
|
||||||
|
disabled={!file}
|
||||||
|
color="blue"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
Import from File
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Import from JSON Section */}
|
||||||
|
<Paper shadow="sm" p="lg" withBorder>
|
||||||
|
<Title order={3} mb="md">Import from JSON</Title>
|
||||||
|
<Text c="dimmed" mb="md">
|
||||||
|
Paste a JSON configuration directly
|
||||||
|
</Text>
|
||||||
|
<Textarea
|
||||||
|
placeholder='{"services": [], "porthijack": [], "firewall": []}'
|
||||||
|
value={configJson}
|
||||||
|
onChange={(e) => setConfigJson(e.currentTarget.value)}
|
||||||
|
minRows={10}
|
||||||
|
maxRows={20}
|
||||||
|
mb="md"
|
||||||
|
styles={{ input: { fontFamily: 'monospace', fontSize: '12px' } }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
leftSection={<FaUpload />}
|
||||||
|
onClick={handleImportJson}
|
||||||
|
loading={importing}
|
||||||
|
disabled={!configJson.trim()}
|
||||||
|
color="blue"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
Import from JSON
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* Import Results */}
|
||||||
|
{importResult && (
|
||||||
|
<Paper shadow="sm" p="lg" withBorder>
|
||||||
|
<Title order={3} mb="md">
|
||||||
|
<Group>
|
||||||
|
{importResult.status === 'ok' ? (
|
||||||
|
<ThemeIcon color="teal" size="lg" radius="xl">
|
||||||
|
<FaCheck />
|
||||||
|
</ThemeIcon>
|
||||||
|
) : (
|
||||||
|
<ThemeIcon color="yellow" size="lg" radius="xl">
|
||||||
|
<FaExclamationTriangle />
|
||||||
|
</ThemeIcon>
|
||||||
|
)}
|
||||||
|
Import Results
|
||||||
|
</Group>
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Stack gap="md">
|
||||||
|
<Group>
|
||||||
|
<Text fw={500}>Services:</Text>
|
||||||
|
<Text c={importResult.services_created > 0 ? "teal" : "dimmed"}>
|
||||||
|
{importResult.services_created} created
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Text fw={500}>PortHijack Services:</Text>
|
||||||
|
<Text c={importResult.porthijack_created > 0 ? "teal" : "dimmed"}>
|
||||||
|
{importResult.porthijack_created} created
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group>
|
||||||
|
<Text fw={500}>Firewall Rules:</Text>
|
||||||
|
<Text c={importResult.firewall_created > 0 ? "teal" : "dimmed"}>
|
||||||
|
{importResult.firewall_created} created
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
{importResult.errors && importResult.errors.length > 0 && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Text fw={500} c="red">Errors:</Text>
|
||||||
|
<List
|
||||||
|
spacing="xs"
|
||||||
|
size="sm"
|
||||||
|
icon={
|
||||||
|
<ThemeIcon color="red" size={20} radius="xl">
|
||||||
|
<FaTimes size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{importResult.errors.map((error: string, idx: number) => (
|
||||||
|
<List.Item key={idx}>
|
||||||
|
<Code>{error}</Code>
|
||||||
|
</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Example Configuration */}
|
||||||
|
<Paper shadow="sm" p="lg" withBorder>
|
||||||
|
<Title order={3} mb="md">Example Configuration</Title>
|
||||||
|
<Text c="dimmed" mb="md">
|
||||||
|
Here's an example of the JSON structure:
|
||||||
|
</Text>
|
||||||
|
<Code block>{`{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "Example HTTP Service",
|
||||||
|
"port": 8080,
|
||||||
|
"proto": "http",
|
||||||
|
"ip_int": "0.0.0.0",
|
||||||
|
"fail_open": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"porthijack": [],
|
||||||
|
"firewall": []
|
||||||
|
}`}</Code>
|
||||||
|
</Paper>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -254,7 +254,6 @@ export default function TrafficViewer() {
|
|||||||
<MdDoubleArrow size="24px" />
|
<MdDoubleArrow size="24px" />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
|||||||
20
setup.example.json
Normal file
20
setup.example.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"name": "Example HTTP Service",
|
||||||
|
"port": 8080,
|
||||||
|
"proto": "http",
|
||||||
|
"ip_int": "0.0.0.0",
|
||||||
|
"fail_open": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Example TCP Service",
|
||||||
|
"port": 443,
|
||||||
|
"proto": "tcp",
|
||||||
|
"ip_int": "0.0.0.0",
|
||||||
|
"fail_open": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"porthijack": [],
|
||||||
|
"firewall": []
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user