From 7f191764be20f14855baf34021d23ee81de0a586 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 8 Feb 2026 17:01:49 +0100 Subject: [PATCH] Update security --- AppImage/components/security.tsx | 238 +++++++++++++++++++--- AppImage/scripts/flask_auth_routes.py | 26 +++ AppImage/scripts/flask_security_routes.py | 24 +++ AppImage/scripts/security_manager.py | 166 ++++++++++++++- 4 files changed, 424 insertions(+), 30 deletions(-) diff --git a/AppImage/components/security.tsx b/AppImage/components/security.tsx index 8c896082..dccbab59 100644 --- a/AppImage/components/security.tsx +++ b/AppImage/components/security.tsx @@ -8,7 +8,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/ import { Shield, Lock, User, AlertCircle, CheckCircle, Info, LogOut, Key, Copy, Eye, EyeOff, Trash2, RefreshCw, Clock, ShieldCheck, Globe, FileKey, AlertTriangle, - Flame, Bug, Search, Download, Power, PowerOff, Plus, Minus, Activity, + Flame, Bug, Search, Download, Power, PowerOff, Plus, Minus, Activity, Settings, Ban, } from "lucide-react" import { getApiUrl, fetchApi } from "../lib/api-config" import { TwoFactorSetup } from "./two-factor-setup" @@ -100,13 +100,17 @@ export function Security() { const [showLynisInstaller, setShowLynisInstaller] = useState(false) // Fail2Ban detailed state + interface BannedIp { + ip: string + type: "local" | "external" | "unknown" + } interface JailDetail { name: string currently_failed: number total_failed: number currently_banned: number total_banned: number - banned_ips: string[] + banned_ips: BannedIp[] findtime: string bantime: string maxretry: string @@ -124,6 +128,11 @@ export function Security() { const [f2bDetailsLoading, setF2bDetailsLoading] = useState(false) const [f2bUnbanning, setF2bUnbanning] = useState(null) const [f2bActiveTab, setF2bActiveTab] = useState<"jails" | "activity">("jails") + const [f2bEditingJail, setF2bEditingJail] = useState(null) + const [f2bJailConfig, setF2bJailConfig] = useState<{maxretry: string; bantime: string; findtime: string; permanent: boolean}>({ + maxretry: "", bantime: "", findtime: "", permanent: false, + }) + const [f2bSavingConfig, setF2bSavingConfig] = useState(false) // SSL/HTTPS state const [sslEnabled, setSslEnabled] = useState(false) @@ -233,6 +242,52 @@ export function Security() { } } + const openJailConfig = (jail: JailDetail) => { + const bt = parseInt(jail.bantime, 10) + const isPermanent = bt === -1 + setF2bEditingJail(jail.name) + setF2bJailConfig({ + maxretry: jail.maxretry, + bantime: isPermanent ? "" : jail.bantime, + findtime: jail.findtime, + permanent: isPermanent, + }) + } + + const handleSaveJailConfig = async () => { + if (!f2bEditingJail) return + setF2bSavingConfig(true) + setError("") + setSuccess("") + try { + const payload: Record = { jail: f2bEditingJail } + if (f2bJailConfig.maxretry) payload.maxretry = parseInt(f2bJailConfig.maxretry, 10) + if (f2bJailConfig.permanent) { + payload.bantime = -1 + } else if (f2bJailConfig.bantime) { + payload.bantime = parseInt(f2bJailConfig.bantime, 10) + } + if (f2bJailConfig.findtime) payload.findtime = parseInt(f2bJailConfig.findtime, 10) + + const data = await fetchApi("/api/security/fail2ban/jail/config", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }) + if (data.success) { + setSuccess(data.message || "Jail configuration updated") + setF2bEditingJail(null) + loadFail2banDetails() + } else { + setError(data.message || "Failed to update jail config") + } + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to update jail config") + } finally { + setF2bSavingConfig(false) + } + } + // Load fail2ban details when basic info shows it's installed and active useEffect(() => { if (fail2banInfo?.installed && fail2banInfo?.active) { @@ -242,6 +297,7 @@ export function Security() { const formatBanTime = (seconds: string) => { const s = parseInt(seconds, 10) + if (s === -1) return "Permanent" if (isNaN(s) || s <= 0) return seconds if (s < 60) return `${s}s` if (s < 3600) return `${Math.floor(s / 60)}m` @@ -1961,7 +2017,7 @@ export function Security() {

Fail2Ban Not Installed

-

Protect SSH and Proxmox web interface from brute force attacks

+

Protect SSH, Proxmox web interface, and ProxMenux Monitor from brute force attacks

@@ -1977,8 +2033,10 @@ export function Security() {
  • SSH protection (max 2 retries, 9h ban)
  • Proxmox web interface protection (port 8006, max 3 retries, 1h ban)
  • +
  • ProxMenux Monitor protection (port 8008 + reverse proxy, max 3 retries, 1h ban)
  • Global settings with nftables backend
+

All settings can be customized after installation. You can change retries, ban time, or set permanent bans.

@@ -2040,28 +2098,28 @@ export function Security() { - {/* Tab switcher */} -
+ {/* Tab switcher - redesigned with border on inactive */} +
@@ -2076,20 +2134,133 @@ export function Security() {
0 ? "bg-red-500 animate-pulse" : "bg-green-500"}`} /> {jail.name} + {parseInt(jail.bantime, 10) === -1 && ( + PERMANENT BAN + )}
-
- - Retries: {jail.maxretry} - - - Ban: {formatBanTime(jail.bantime)} - - - Window: {formatBanTime(jail.findtime)} - +
+
+ + Retries: {jail.maxretry} + + + Ban: {parseInt(jail.bantime, 10) === -1 ? "Permanent" : formatBanTime(jail.bantime)} + + + Window: {formatBanTime(jail.findtime)} + +
+
+ {/* Jail config editor */} + {f2bEditingJail === jail.name && ( +
+
+ +

Configure {jail.name}

+
+ +
+
+ + setF2bJailConfig({...f2bJailConfig, maxretry: e.target.value})} + className="h-9 text-sm" + placeholder="e.g. 3" + /> +

Failed attempts before ban

+
+
+ + setF2bJailConfig({...f2bJailConfig, bantime: e.target.value, permanent: false})} + className="h-9 text-sm" + placeholder={f2bJailConfig.permanent ? "Permanent" : "e.g. 3600 = 1h"} + disabled={f2bJailConfig.permanent} + /> +
+ setF2bJailConfig({...f2bJailConfig, permanent: e.target.checked, bantime: ""})} + className="rounded border-border" + /> + +
+
+
+ + setF2bJailConfig({...f2bJailConfig, findtime: e.target.value})} + className="h-9 text-sm" + placeholder="e.g. 600 = 10m" + /> +

Time window for counting retries

+
+
+ +
+ +

+ Common values: 600s = 10min, 3600s = 1h, 32400s = 9h, 86400s = 24h. Set ban to permanent if you want blocked IPs to stay blocked until you manually unban them. +

+
+ +
+ + +
+
+ )} + + {/* Mobile config summary (visible only on small screens) */} +
+ Retries: {jail.maxretry} + Ban: {parseInt(jail.bantime, 10) === -1 ? "Perm" : formatBanTime(jail.bantime)} + Window: {formatBanTime(jail.findtime)} +
+ {/* Jail stats bar */}
@@ -2120,20 +2291,29 @@ export function Security() { Banned IPs ({jail.banned_ips.length})

- {jail.banned_ips.map((ip) => ( -
+ {jail.banned_ips.map((entry) => ( +
- {ip} + {entry.ip} + + {entry.type === "local" ? "LAN" : entry.type === "external" ? "External" : "Unknown"} +