mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-02-19 00:46:31 +00:00
Update fail2ban
This commit is contained in:
@@ -133,6 +133,7 @@ export function Security() {
|
|||||||
maxretry: "", bantime: "", findtime: "", permanent: false,
|
maxretry: "", bantime: "", findtime: "", permanent: false,
|
||||||
})
|
})
|
||||||
const [f2bSavingConfig, setF2bSavingConfig] = useState(false)
|
const [f2bSavingConfig, setF2bSavingConfig] = useState(false)
|
||||||
|
const [f2bApplyingJails, setF2bApplyingJails] = useState(false)
|
||||||
|
|
||||||
// SSL/HTTPS state
|
// SSL/HTTPS state
|
||||||
const [sslEnabled, setSslEnabled] = useState(false)
|
const [sslEnabled, setSslEnabled] = useState(false)
|
||||||
@@ -242,6 +243,29 @@ export function Security() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleApplyMissingJails = async () => {
|
||||||
|
setF2bApplyingJails(true)
|
||||||
|
setError("")
|
||||||
|
setSuccess("")
|
||||||
|
try {
|
||||||
|
const data = await fetchApi("/api/security/fail2ban/apply-jails", {
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
if (data.success) {
|
||||||
|
setSuccess(data.message || "Missing jails applied successfully")
|
||||||
|
// Reload to see the new jails
|
||||||
|
await loadFail2banDetails()
|
||||||
|
loadSecurityTools()
|
||||||
|
} else {
|
||||||
|
setError(data.message || "Failed to apply missing jails")
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : "Failed to apply missing jails")
|
||||||
|
} finally {
|
||||||
|
setF2bApplyingJails(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openJailConfig = (jail: JailDetail) => {
|
const openJailConfig = (jail: JailDetail) => {
|
||||||
const bt = parseInt(jail.bantime, 10)
|
const bt = parseInt(jail.bantime, 10)
|
||||||
const isPermanent = bt === -1
|
const isPermanent = bt === -1
|
||||||
@@ -2098,6 +2122,50 @@ export function Security() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Missing jails warning */}
|
||||||
|
{(() => {
|
||||||
|
const expectedJails = ["sshd", "proxmox", "proxmenux"]
|
||||||
|
const currentNames = f2bDetails.jails.map(j => j.name.toLowerCase())
|
||||||
|
const missing = expectedJails.filter(j => !currentNames.includes(j))
|
||||||
|
if (missing.length === 0) return null
|
||||||
|
|
||||||
|
const jailLabels: Record<string, string> = {
|
||||||
|
sshd: "SSH (sshd)",
|
||||||
|
proxmox: "Proxmox UI (port 8006)",
|
||||||
|
proxmenux: "ProxMenux Monitor (port 8008)",
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-yellow-500/10 border border-yellow-500/20 rounded-lg p-4">
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<AlertTriangle className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm font-medium text-yellow-500">Missing protections detected</p>
|
||||||
|
<p className="text-xs text-yellow-400/80">
|
||||||
|
The following jails are not configured:{" "}
|
||||||
|
{missing.map(j => jailLabels[j] || j).join(", ")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
disabled={f2bApplyingJails}
|
||||||
|
onClick={handleApplyMissingJails}
|
||||||
|
className="bg-yellow-600 hover:bg-yellow-700 text-white flex-shrink-0"
|
||||||
|
>
|
||||||
|
{f2bApplyingJails ? (
|
||||||
|
<div className="animate-spin h-3.5 w-3.5 border-2 border-white border-t-transparent rounded-full mr-1.5" />
|
||||||
|
) : (
|
||||||
|
<Shield className="h-3.5 w-3.5 mr-1.5" />
|
||||||
|
)}
|
||||||
|
Apply Missing Jails
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
|
||||||
{/* Tab switcher - redesigned with border on inactive */}
|
{/* Tab switcher - redesigned with border on inactive */}
|
||||||
<div className="flex gap-0 rounded-lg border border-border overflow-hidden">
|
<div className="flex gap-0 rounded-lg border border-border overflow-hidden">
|
||||||
<button
|
<button
|
||||||
@@ -2134,6 +2202,12 @@ export function Security() {
|
|||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className={`w-2.5 h-2.5 rounded-full ${jail.currently_banned > 0 ? "bg-red-500 animate-pulse" : "bg-green-500"}`} />
|
<div className={`w-2.5 h-2.5 rounded-full ${jail.currently_banned > 0 ? "bg-red-500 animate-pulse" : "bg-green-500"}`} />
|
||||||
<span className="font-semibold text-sm">{jail.name}</span>
|
<span className="font-semibold text-sm">{jail.name}</span>
|
||||||
|
<span className="text-[10px] text-muted-foreground">
|
||||||
|
{jail.name === "sshd" ? "SSH Remote Access" :
|
||||||
|
jail.name === "proxmox" ? "Proxmox UI :8006" :
|
||||||
|
jail.name === "proxmenux" ? "ProxMenux Monitor :8008" :
|
||||||
|
""}
|
||||||
|
</span>
|
||||||
{parseInt(jail.bantime, 10) === -1 && (
|
{parseInt(jail.bantime, 10) === -1 && (
|
||||||
<span className="px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-500/10 text-red-500">PERMANENT BAN</span>
|
<span className="px-1.5 py-0.5 rounded text-[10px] font-bold bg-red-500/10 text-red-500">PERMANENT BAN</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -188,6 +188,18 @@ def fail2ban_jail_config():
|
|||||||
return jsonify({"success": False, "message": str(e)}), 500
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@security_bp.route('/api/security/fail2ban/apply-jails', methods=['POST'])
|
||||||
|
def fail2ban_apply_jails():
|
||||||
|
"""Apply missing Fail2Ban jails (proxmox, proxmenux)"""
|
||||||
|
if not security_manager:
|
||||||
|
return jsonify({"success": False, "message": "Security manager not available"}), 500
|
||||||
|
try:
|
||||||
|
success, message, applied = security_manager.apply_missing_jails()
|
||||||
|
return jsonify({"success": success, "message": message, "applied": applied})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"success": False, "message": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@security_bp.route('/api/security/fail2ban/activity', methods=['GET'])
|
@security_bp.route('/api/security/fail2ban/activity', methods=['GET'])
|
||||||
def fail2ban_activity():
|
def fail2ban_activity():
|
||||||
"""Get recent Fail2Ban log activity"""
|
"""Get recent Fail2Ban log activity"""
|
||||||
|
|||||||
@@ -768,6 +768,102 @@ def _persist_jail_config(jail_name, maxretry=None, bantime=None, findtime=None):
|
|||||||
pass # Best effort persistence
|
pass # Best effort persistence
|
||||||
|
|
||||||
|
|
||||||
|
def apply_missing_jails():
|
||||||
|
"""
|
||||||
|
Check for missing Fail2Ban jails (proxmox, proxmenux) and create them.
|
||||||
|
Returns (success, message, applied_jails).
|
||||||
|
"""
|
||||||
|
applied = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Check which jails are currently active
|
||||||
|
rc, out, _ = _run_cmd(["fail2ban-client", "status"])
|
||||||
|
if rc != 0:
|
||||||
|
return False, "Cannot communicate with fail2ban-client", []
|
||||||
|
|
||||||
|
current_jails = []
|
||||||
|
for line in out.splitlines():
|
||||||
|
if "Jail list:" in line:
|
||||||
|
jails_str = line.split(":", 1)[1].strip()
|
||||||
|
current_jails = [j.strip().lower() for j in jails_str.split(",") if j.strip()]
|
||||||
|
|
||||||
|
# --- Proxmox jail (port 8006) ---
|
||||||
|
if "proxmox" not in current_jails:
|
||||||
|
try:
|
||||||
|
# Create filter
|
||||||
|
filter_content = """[Definition]
|
||||||
|
failregex = pvedaemon\\[.*authentication failure; rhost=<HOST> user=.* msg=.*
|
||||||
|
ignoreregex =
|
||||||
|
"""
|
||||||
|
with open("/etc/fail2ban/filter.d/proxmox.conf", "w") as f:
|
||||||
|
f.write(filter_content)
|
||||||
|
|
||||||
|
# Create jail
|
||||||
|
jail_content = """[proxmox]
|
||||||
|
enabled = true
|
||||||
|
port = 8006
|
||||||
|
filter = proxmox
|
||||||
|
logpath = /var/log/daemon.log
|
||||||
|
maxretry = 3
|
||||||
|
bantime = 3600
|
||||||
|
findtime = 600
|
||||||
|
"""
|
||||||
|
with open("/etc/fail2ban/jail.d/proxmox.conf", "w") as f:
|
||||||
|
f.write(jail_content)
|
||||||
|
|
||||||
|
applied.append("proxmox")
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"proxmox: {str(e)}")
|
||||||
|
|
||||||
|
# --- ProxMenux Monitor jail (port 8008 + reverse proxy) ---
|
||||||
|
if "proxmenux" not in current_jails:
|
||||||
|
try:
|
||||||
|
# Create filter
|
||||||
|
filter_content = """[Definition]
|
||||||
|
failregex = proxmenux-auth: authentication failure; rhost=<HOST> user=.*
|
||||||
|
ignoreregex =
|
||||||
|
"""
|
||||||
|
with open("/etc/fail2ban/filter.d/proxmenux.conf", "w") as f:
|
||||||
|
f.write(filter_content)
|
||||||
|
|
||||||
|
# Create jail
|
||||||
|
jail_content = """[proxmenux]
|
||||||
|
enabled = true
|
||||||
|
port = 8008,http,https
|
||||||
|
filter = proxmenux
|
||||||
|
logpath = /var/log/proxmenux-auth.log
|
||||||
|
maxretry = 3
|
||||||
|
bantime = 3600
|
||||||
|
findtime = 600
|
||||||
|
"""
|
||||||
|
with open("/etc/fail2ban/jail.d/proxmenux.conf", "w") as f:
|
||||||
|
f.write(jail_content)
|
||||||
|
|
||||||
|
# Ensure log file exists
|
||||||
|
if not os.path.isfile("/var/log/proxmenux-auth.log"):
|
||||||
|
with open("/var/log/proxmenux-auth.log", "w") as f:
|
||||||
|
pass
|
||||||
|
os.chmod("/var/log/proxmenux-auth.log", 0o640)
|
||||||
|
|
||||||
|
applied.append("proxmenux")
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(f"proxmenux: {str(e)}")
|
||||||
|
|
||||||
|
if not applied and not errors:
|
||||||
|
return True, "All jails are already configured", []
|
||||||
|
|
||||||
|
if applied:
|
||||||
|
# Restart fail2ban to load new jails
|
||||||
|
_run_cmd(["systemctl", "restart", "fail2ban"])
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return False, "Errors: " + "; ".join(errors), applied
|
||||||
|
|
||||||
|
return True, f"Applied jails: {', '.join(applied)}", applied
|
||||||
|
|
||||||
|
|
||||||
def unban_ip(jail_name, ip_address):
|
def unban_ip(jail_name, ip_address):
|
||||||
"""
|
"""
|
||||||
Unban a specific IP from a Fail2Ban jail.
|
Unban a specific IP from a Fail2Ban jail.
|
||||||
|
|||||||
Reference in New Issue
Block a user