mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-15 19:26:45 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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" '{}'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user