Update logs

This commit is contained in:
MacRimi
2026-02-16 09:52:33 +01:00
7 changed files with 764 additions and 101 deletions

View File

@@ -4,6 +4,10 @@ Provides REST API endpoints for authentication management
"""
import logging
import os
import subprocess
import threading
import time
from flask import Blueprint, jsonify, request
import auth_manager
import jwt
@@ -73,12 +77,36 @@ def ssl_status():
return jsonify({"success": False, "message": str(e)}), 500
def _schedule_service_restart(delay=1.5):
"""Schedule a restart of the monitor service via systemctl after a short delay.
This gives time for the HTTP response to reach the client before the process restarts."""
def _do_restart():
time.sleep(delay)
print("[ProxMenux] Restarting monitor service to apply SSL changes...")
# Use systemctl restart which properly stops and starts the service.
# This works because systemd manages proxmenux-monitor.service.
try:
subprocess.Popen(
["systemctl", "restart", "proxmenux-monitor"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
except Exception as e:
print(f"[ProxMenux] Failed to restart via systemctl: {e}")
# Fallback: try to restart the process directly
os.kill(os.getpid(), 15) # SIGTERM
t = threading.Thread(target=_do_restart, daemon=True)
t.start()
@auth_bp.route('/api/ssl/configure', methods=['POST'])
def ssl_configure():
"""Configure SSL with Proxmox or custom certificates"""
try:
data = request.json or {}
source = data.get("source", "proxmox")
auto_restart = data.get("auto_restart", True)
if source == "proxmox":
cert_path = auth_manager.PROXMOX_CERT_PATH
@@ -92,7 +120,14 @@ def ssl_configure():
success, message = auth_manager.configure_ssl(cert_path, key_path, source)
if success:
return jsonify({"success": True, "message": message, "requires_restart": True})
if auto_restart:
_schedule_service_restart()
return jsonify({
"success": True,
"message": "SSL enabled. The service is restarting...",
"restarting": auto_restart,
"new_protocol": "https"
})
else:
return jsonify({"success": False, "message": message}), 400
except Exception as e:
@@ -103,10 +138,20 @@ def ssl_configure():
def ssl_disable():
"""Disable SSL and return to HTTP"""
try:
data = request.json or {}
auto_restart = data.get("auto_restart", True)
success, message = auth_manager.disable_ssl()
if success:
return jsonify({"success": True, "message": message, "requires_restart": True})
if auto_restart:
_schedule_service_restart()
return jsonify({
"success": True,
"message": "SSL disabled. The service is restarting...",
"restarting": auto_restart,
"new_protocol": "http"
})
else:
return jsonify({"success": False, "message": message}), 400
except Exception as e:

View File

@@ -106,6 +106,39 @@ def firewall_delete_rule():
return jsonify({"success": False, "message": str(e)}), 500
@security_bp.route('/api/security/firewall/rules/edit', methods=['PUT'])
def firewall_edit_rule():
"""Edit an existing firewall rule (delete old + insert new at same position)"""
if not security_manager:
return jsonify({"success": False, "message": "Security manager not available"}), 500
try:
data = request.json or {}
rule_index = data.get("rule_index")
level = data.get("level", "host")
new_rule = data.get("new_rule", {})
if rule_index is None:
return jsonify({"success": False, "message": "rule_index is required"}), 400
success, message = security_manager.edit_firewall_rule(
rule_index=int(rule_index),
level=level,
direction=new_rule.get("direction", "IN"),
action=new_rule.get("action", "ACCEPT"),
protocol=new_rule.get("protocol", "tcp"),
dport=new_rule.get("dport", ""),
sport=new_rule.get("sport", ""),
source=new_rule.get("source", ""),
iface=new_rule.get("iface", ""),
comment=new_rule.get("comment", ""),
)
if success:
return jsonify({"success": True, "message": message})
else:
return jsonify({"success": False, "message": message}), 400
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
@security_bp.route('/api/security/firewall/monitor-port', methods=['POST'])
def firewall_add_monitor_port():
"""Add firewall rule to allow port 8008 for ProxMenux Monitor"""

View File

@@ -274,6 +274,96 @@ def add_firewall_rule(direction="IN", action="ACCEPT", protocol="tcp", dport="",
return False, f"Failed to add firewall rule: {str(e)}"
def edit_firewall_rule(rule_index, level="host", direction="IN", action="ACCEPT",
protocol="tcp", dport="", sport="", source="", iface="", comment=""):
"""
Edit an existing firewall rule by replacing it in-place.
Deletes the old rule at rule_index and inserts the new one at the same position.
Returns (success, message)
"""
# Validate inputs
action = action.upper()
if action not in ("ACCEPT", "DROP", "REJECT"):
return False, f"Invalid action: {action}. Must be ACCEPT, DROP, or REJECT"
direction = direction.upper()
if direction not in ("IN", "OUT"):
return False, f"Invalid direction: {direction}. Must be IN or OUT"
# Build new rule line
parts = [direction, action]
if protocol:
parts.extend(["-p", protocol.lower()])
if dport:
if not re.match(r'^[\d:,]+$', dport):
return False, f"Invalid destination port: {dport}"
parts.extend(["-dport", dport])
if sport:
if not re.match(r'^[\d:,]+$', sport):
return False, f"Invalid source port: {sport}"
parts.extend(["-sport", sport])
if source:
parts.extend(["-source", source])
if iface:
parts.extend(["-i", iface])
parts.extend(["-log", "nolog"])
if comment:
safe_comment = re.sub(r'[^\w\s\-._/():]', '', comment)
parts.append(f"# {safe_comment}")
new_rule_line = " ".join(parts)
# Determine target file
if level == "cluster":
fw_file = CLUSTER_FW
else:
fw_file = os.path.join(HOST_FW_DIR, "host.fw")
if not os.path.isfile(fw_file):
return False, "Firewall config file not found"
try:
with open(fw_file, 'r') as f:
content = f.read()
lines = content.splitlines()
new_lines = []
in_rules = False
current_rule_idx = 0
replaced = False
for line in lines:
stripped = line.strip()
if stripped.startswith('['):
section_match = re.match(r'\[(\w+)\]', stripped)
if section_match:
section = section_match.group(1).upper()
in_rules = section in ("RULES", "IN", "OUT")
if in_rules and stripped and not stripped.startswith('#') and not stripped.startswith('['):
if current_rule_idx == rule_index:
# Replace the old rule with the new one
new_lines.append(new_rule_line)
replaced = True
current_rule_idx += 1
continue
current_rule_idx += 1
new_lines.append(line)
if not replaced:
return False, f"Rule index {rule_index} not found"
with open(fw_file, 'w') as f:
f.write("\n".join(new_lines) + "\n")
_run_cmd(["pve-firewall", "reload"])
return True, f"Firewall rule updated: {direction} {action} {protocol}{':' + dport if dport else ''}"
except PermissionError:
return False, "Permission denied. Cannot modify firewall config."
except Exception as e:
return False, f"Failed to edit rule: {str(e)}"
def delete_firewall_rule(rule_index, level="host"):
"""
Delete a firewall rule by index from host or cluster config.