Add audit and reports page, and a change journal

ProxMenux modifies the host: it rewrites configuration files, installs packages, enables services. Until now nobody could say afterwards what had changed, and showing the script does not answer that question — a four-hundred-line function may alter two values, and the reader has no way to know which two. This adds the two halves of an answer.

The change journal records what ProxMenux does as it does it. Eleven bash primitives capture the previous state, apply the change and record it in the same step, writing to a spool that the Monitor reads back. One hundred and thirteen functions across twenty-five scripts are instrumented, covering post-install, shared storage, security tooling, container conversions, disk operations and the PVE 8 to 9 upgrade path. The page shows the difference — rotate 7 becoming rotate 14 — and never the script. Restore and backup scripts are deliberately left out: a restore puts the host back to a state some other script already recorded.

The Audit and reports page answers the other half: what state is this host in, regardless of who put it there. Forty-three checks across seven areas read the host and classify each result as critical, warning, observation, conformant, unverified or not applicable, with the evidence they read attached to each one. A declared policy lets the reader say what this particular host is expected to do — which guests must have a backup, which storages are essential — so the report judges the host against its own intent rather than a generic template. An inventory records the hardware, network and guest topology behind those readings, a comparison shows what moved between two runs, and six report profiles produce a printable document scoped to what the reader needs. Everything is available in the eight supported languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-08 21:06:04 +02:00
co-authored by Claude Opus 5
parent ae75508eff
commit da8a480eff
102 changed files with 24118 additions and 1403 deletions
+49 -33
View File
@@ -48,6 +48,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
echo "{}" > "$COMPONENTS_STATUS_FILE"
fi
@@ -79,6 +83,9 @@ detect_fail2ban() {
# Installation
# ==========================================================
install_fail2ban() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_fail2ban" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Installing and configuring Fail2Ban to protect Proxmox web interface and SSH...")"
@@ -90,7 +97,7 @@ install_fail2ban() {
if ! grep -RqsE "debian.*(bookworm|trixie)" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null; then
msg_warn "$(translate "Debian repositories missing; creating default source file")"
local src="/etc/apt/sources.list.d/debian.sources"
cat > "$src" <<EOF
pmx_write_file "$src" <<EOF
Types: deb
URIs: http://deb.debian.org/debian
Suites: ${deb_codename} ${deb_codename}-updates
@@ -107,7 +114,7 @@ EOF
# Install Fail2Ban
msg_info "$(translate "Installing Fail2Ban...")"
if ! DEBIAN_FRONTEND=noninteractive apt-get update -y >/dev/null 2>&1 || \
! DEBIAN_FRONTEND=noninteractive apt-get install -y fail2ban >/dev/null 2>&1; then
! pmx_install_pkg fail2ban; then
msg_error "$(translate "Failed to install Fail2Ban")"
return 1
fi
@@ -132,7 +139,7 @@ EOF
# Create a drop-in so we don't break other Proxmox settings
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/proxmenux-loglevel.conf <<'JEOF'
pmx_write_file /etc/systemd/journald.conf.d/proxmenux-loglevel.conf <<'JEOF'
# ProxMenux: Allow auth/info messages so Fail2Ban can detect SSH failures
# Proxmox default MaxLevelStore=warning drops PAM/SSH auth events
[Journal]
@@ -148,6 +155,7 @@ JEOF
esac
if $journald_changed; then
pmx_record_execution "restart systemd-journald" "systemctl restart systemd-journald"
systemctl restart systemd-journald
sleep 1
msg_ok "$(translate "journald restarted - auth messages will now be stored")"
@@ -163,7 +171,7 @@ JEOF
# -- Proxmox UI auth logger (pvedaemon) --
msg_info "$(translate "Creating Proxmox auth logger service...")"
cat > /etc/systemd/system/proxmox-auth-logger.service <<'EOF'
pmx_write_file /etc/systemd/system/proxmox-auth-logger.service <<'EOF'
[Unit]
Description=Proxmox Auth Logger for Fail2Ban
Documentation=https://github.com/MacRimi/ProxMenux
@@ -185,12 +193,12 @@ EOF
chown root:adm /var/log/proxmox-auth.log 2>/dev/null || true
systemctl daemon-reload
systemctl enable --now proxmox-auth-logger.service >/dev/null 2>&1
pmx_enable_service proxmox-auth-logger.service
msg_ok "$(translate "Proxmox auth logger service created and started")"
# -- SSH auth logger --
msg_info "$(translate "Creating SSH auth logger service...")"
cat > /etc/systemd/system/ssh-auth-logger.service <<'EOF'
pmx_write_file /etc/systemd/system/ssh-auth-logger.service <<'EOF'
[Unit]
Description=SSH Auth Logger for Fail2Ban
Documentation=https://github.com/MacRimi/ProxMenux
@@ -212,13 +220,13 @@ EOF
chown root:adm /var/log/ssh-auth.log 2>/dev/null || true
systemctl daemon-reload
systemctl enable --now ssh-auth-logger.service >/dev/null 2>&1
pmx_enable_service ssh-auth-logger.service
msg_ok "$(translate "SSH auth logger service created and started")"
# Configure Proxmox filter
mkdir -p /etc/fail2ban/filter.d /etc/fail2ban/jail.d
msg_info "$(translate "Configuring Proxmox filter...")"
cat > /etc/fail2ban/filter.d/proxmox.conf <<'EOF'
pmx_write_file /etc/fail2ban/filter.d/proxmox.conf <<'EOF'
[Definition]
# The proxmox-auth-logger service writes journal lines to /var/log/proxmox-auth.log
# in short-iso format: 2026-02-10T19:36:08+01:00 host pvedaemon[PID]: message
@@ -231,7 +239,7 @@ EOF
# Configure Proxmox jail (file-based backend)
msg_info "$(translate "Configuring Proxmox jail...")"
cat > /etc/fail2ban/jail.d/proxmox.conf <<'EOF'
pmx_write_file /etc/fail2ban/jail.d/proxmox.conf <<'EOF'
[proxmox]
enabled = true
port = 8006
@@ -248,7 +256,7 @@ EOF
# This reads from a file written directly by the Flask app (not syslog/journal),
# so it uses a datepattern that matches Python's logging format.
msg_info "$(translate "Configuring ProxMenux Monitor filter...")"
cat > /etc/fail2ban/filter.d/proxmenux.conf <<'EOF'
pmx_write_file /etc/fail2ban/filter.d/proxmenux.conf <<'EOF'
[Definition]
failregex = ^.*proxmenux-auth: authentication failure; rhost=<HOST> user=.*$
ignoreregex =
@@ -259,7 +267,7 @@ EOF
# Configure ProxMenux Monitor jail (port 8008 + http/https for reverse proxy)
# Uses backend=auto with logpath because the Flask app writes directly to this file.
msg_info "$(translate "Configuring ProxMenux Monitor jail...")"
cat > /etc/fail2ban/jail.d/proxmenux.conf <<'EOF'
pmx_write_file /etc/fail2ban/jail.d/proxmenux.conf <<'EOF'
[proxmenux]
enabled = true
port = 8008,http,https
@@ -289,7 +297,7 @@ EOF
# Configure global settings and SSH jail
msg_info "$(translate "Configuring global Fail2Ban settings and SSH jail...")"
cat > /etc/fail2ban/jail.local <<EOF
pmx_write_file /etc/fail2ban/jail.local <<EOF
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
ignoreself = true
@@ -325,18 +333,19 @@ EOF
fi
# Store original value in our config directory
echo "$original_max_auth" > "${BASE_DIR}/sshd_maxauthtries_backup"
printf '%s\n' "$original_max_auth" | pmx_write_file "${BASE_DIR}/sshd_maxauthtries_backup"
msg_info "$(translate "Hardening SSH: setting MaxAuthTries to 3...")"
if grep -qi '^MaxAuthTries' "$sshd_config"; then
sed -i 's/^MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
pmx_edit_file "$sshd_config" 's/^MaxAuthTries.*/MaxAuthTries 3/'
elif grep -qi '^#MaxAuthTries' "$sshd_config"; then
sed -i 's/^#MaxAuthTries.*/MaxAuthTries 3/' "$sshd_config"
pmx_edit_file "$sshd_config" 's/^#MaxAuthTries.*/MaxAuthTries 3/'
else
echo "MaxAuthTries 3" >> "$sshd_config"
echo "MaxAuthTries 3" | pmx_append_file "$sshd_config"
fi
# Reload SSH to apply the change (reload, not restart, to keep existing sessions)
pmx_record_execution "reload SSH service" "systemctl reload sshd or ssh"
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
msg_ok "$(translate "SSH MaxAuthTries set to 3 (original: ${original_max_auth})")"
fi
@@ -344,7 +353,9 @@ EOF
# Enable and restart the service (restart ensures new jails are loaded
# even if fail2ban was already running from a previous install)
systemctl daemon-reload
systemctl enable fail2ban >/dev/null 2>&1
pmx_apply_setting "fail2ban enabled state" "systemctl is-enabled fail2ban 2>/dev/null || true" \
systemctl enable fail2ban
pmx_record_execution "restart fail2ban" "systemctl restart fail2ban"
systemctl restart fail2ban >/dev/null 2>&1
sleep 3
@@ -372,29 +383,32 @@ EOF
# Uninstall
# ==========================================================
uninstall_fail2ban() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_fail2ban" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Removing Fail2Ban...")"
systemctl stop fail2ban 2>/dev/null || true
systemctl disable fail2ban 2>/dev/null || true
pmx_disable_service fail2ban 2>/dev/null || true
# Stop and remove the auth logger services
systemctl stop proxmox-auth-logger.service 2>/dev/null || true
systemctl disable proxmox-auth-logger.service 2>/dev/null || true
rm -f /etc/systemd/system/proxmox-auth-logger.service
systemctl stop ssh-auth-logger.service 2>/dev/null || true
systemctl disable ssh-auth-logger.service 2>/dev/null || true
rm -f /etc/systemd/system/ssh-auth-logger.service
pmx_disable_service proxmox-auth-logger.service 2>/dev/null || true
pmx_remove_file /etc/systemd/system/proxmox-auth-logger.service
pmx_disable_service ssh-auth-logger.service 2>/dev/null || true
pmx_remove_file /etc/systemd/system/ssh-auth-logger.service
systemctl daemon-reload 2>/dev/null || true
pmx_record_execution "remove Fail2Ban auth logger files" \
"rm -f /var/log/proxmox-auth.log /var/log/ssh-auth.log"
rm -f /var/log/proxmox-auth.log /var/log/ssh-auth.log
pmx_record_execution "purge fail2ban package" "apt-get purge -y fail2ban"
DEBIAN_FRONTEND=noninteractive apt-get purge -y fail2ban >/dev/null 2>&1
rm -f /etc/fail2ban/jail.d/proxmox.conf
rm -f /etc/fail2ban/jail.d/proxmenux.conf
rm -f /etc/fail2ban/filter.d/proxmox.conf
rm -f /etc/fail2ban/filter.d/proxmenux.conf
rm -f /etc/fail2ban/jail.local
pmx_remove_file /etc/fail2ban/jail.d/proxmox.conf
pmx_remove_file /etc/fail2ban/jail.d/proxmenux.conf
pmx_remove_file /etc/fail2ban/filter.d/proxmox.conf
pmx_remove_file /etc/fail2ban/filter.d/proxmenux.conf
pmx_remove_file /etc/fail2ban/jail.local
# ── Restore SSH MaxAuthTries to original value ──
local sshd_config="/etc/ssh/sshd_config"
@@ -405,17 +419,19 @@ uninstall_fail2ban() {
if [[ -n "$original_val" ]]; then
msg_info "$(translate "Restoring SSH MaxAuthTries to ${original_val}...")"
if grep -qi '^MaxAuthTries' "$sshd_config"; then
sed -i "s/^MaxAuthTries.*/MaxAuthTries ${original_val}/" "$sshd_config"
pmx_edit_file "$sshd_config" "s/^MaxAuthTries.*/MaxAuthTries ${original_val}/"
fi
pmx_record_execution "reload SSH service" "systemctl reload sshd or ssh"
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
msg_ok "$(translate "SSH MaxAuthTries restored to ${original_val}")"
fi
rm -f "$backup_file"
pmx_remove_file "$backup_file"
fi
# Remove journald drop-in and restore original log level
if [[ -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf ]]; then
rm -f /etc/systemd/journald.conf.d/proxmenux-loglevel.conf
pmx_remove_file /etc/systemd/journald.conf.d/proxmenux-loglevel.conf
pmx_record_execution "restart systemd-journald" "systemctl restart systemd-journald"
systemctl restart systemd-journald 2>/dev/null || true
msg_ok "$(translate "journald log level restored")"
fi
+24 -3
View File
@@ -46,6 +46,10 @@ if [[ -f "$UTILS_FILE" ]]; then
source "$UTILS_FILE"
fi
if [[ -f "$LOCAL_SCRIPTS/global/pmx_journal.sh" ]]; then
source "$LOCAL_SCRIPTS/global/pmx_journal.sh"
fi
if [[ ! -f "$COMPONENTS_STATUS_FILE" ]]; then
echo "{}" > "$COMPONENTS_STATUS_FILE"
fi
@@ -80,6 +84,9 @@ detect_lynis() {
# Installation
# ==========================================================
install_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "install_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Installing latest Lynis security scan tool...")"
@@ -91,7 +98,7 @@ install_lynis() {
if ! command -v git >/dev/null 2>&1; then
msg_info "$(translate "Installing Git as a prerequisite...")"
apt-get update -qq >/dev/null 2>&1
if apt-get install -y git >/dev/null 2>&1 && command -v git >/dev/null 2>&1; then
if pmx_install_pkg git && command -v git >/dev/null 2>&1; then
msg_ok "$(translate "Git installed")"
else
msg_error "$(translate "Could not install Git — Lynis cannot be cloned. Run 'apt-get install git' manually.")"
@@ -102,15 +109,17 @@ install_lynis() {
# Remove old installation if present
if [[ -d /opt/lynis ]]; then
msg_info "$(translate "Removing previous Lynis installation...")"
pmx_record_execution "remove previous Lynis installation from /opt/lynis" "rm -rf /opt/lynis"
rm -rf /opt/lynis >/dev/null 2>&1
msg_ok "$(translate "Previous installation removed")"
fi
# Clone from GitHub
msg_info "$(translate "Cloning Lynis from GitHub...")"
pmx_record_execution "install Lynis in /opt/lynis" "git clone https://github.com/CISOfy/lynis.git /opt/lynis"
if git clone --quiet https://github.com/CISOfy/lynis.git /opt/lynis >/dev/null 2>&1; then
# Create wrapper script
cat << 'EOF' > /usr/local/bin/lynis
pmx_write_file /usr/local/bin/lynis << 'EOF'
#!/bin/bash
cd /opt/lynis && ./lynis "$@"
EOF
@@ -144,6 +153,9 @@ EOF
# Update
# ==========================================================
update_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "update_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Updating Lynis to the latest version...")"
@@ -151,6 +163,7 @@ update_lynis() {
if [[ -d /opt/lynis/.git ]]; then
cd /opt/lynis
msg_info "$(translate "Pulling latest changes from GitHub...")"
pmx_record_execution "update Lynis installation in /opt/lynis" "git pull --quiet"
if git pull --quiet >/dev/null 2>&1; then
local version
version=$(/usr/local/bin/lynis show version 2>/dev/null)
@@ -174,6 +187,9 @@ update_lynis() {
# Run Audit
# ==========================================================
run_audit() {
local FUNC_VERSION="1.0"
pmx_journal_context "run_audit" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Running Lynis security audit...")"
@@ -185,6 +201,7 @@ run_audit() {
fi
# Run the audit
pmx_record_execution "run Lynis system audit" "$LYNIS_CMD audit system --no-colors"
"$LYNIS_CMD" audit system --no-colors 2>&1
echo ""
@@ -197,12 +214,16 @@ run_audit() {
# Uninstall
# ==========================================================
uninstall_lynis() {
local FUNC_VERSION="1.0"
pmx_journal_context "uninstall_lynis" "$FUNC_VERSION"
show_proxmenux_logo
msg_title "$(translate "$SCRIPT_TITLE")"
msg_info2 "$(translate "Removing Lynis...")"
pmx_record_execution "remove Lynis installation from /opt/lynis" "rm -rf /opt/lynis"
rm -rf /opt/lynis 2>/dev/null
rm -f /usr/local/bin/lynis 2>/dev/null
pmx_remove_file /usr/local/bin/lynis 2>/dev/null
update_component_status "lynis" "removed" "" "security" '{}'