diff --git a/scripts/install-sensor-proxy.sh b/scripts/install-sensor-proxy.sh index 3b28a7b..34a567c 100755 --- a/scripts/install-sensor-proxy.sh +++ b/scripts/install-sensor-proxy.sh @@ -305,6 +305,200 @@ fi print_info "Socket ready at $SOCKET_PATH" +# Install cleanup system for automatic SSH key removal when nodes are deleted +print_info "Installing cleanup system..." + +# Install cleanup script +CLEANUP_SCRIPT_PATH="/usr/local/bin/pulse-sensor-cleanup.sh" +cat > "$CLEANUP_SCRIPT_PATH" << 'CLEANUP_EOF' +#!/bin/bash + +# pulse-sensor-cleanup.sh - Removes Pulse SSH keys from Proxmox nodes when they're removed from Pulse +# This script is triggered by systemd path unit when cleanup-request.json is created + +set -euo pipefail + +# Configuration +WORK_DIR="/var/lib/pulse-sensor-proxy" +CLEANUP_REQUEST="${WORK_DIR}/cleanup-request.json" +LOG_TAG="pulse-sensor-cleanup" + +# Logging functions +log_info() { + logger -t "$LOG_TAG" -p user.info "$1" + echo "[INFO] $1" +} + +log_warn() { + logger -t "$LOG_TAG" -p user.warning "$1" + echo "[WARN] $1" +} + +log_error() { + logger -t "$LOG_TAG" -p user.err "$1" + echo "[ERROR] $1" >&2 +} + +# Check if cleanup request file exists +if [[ ! -f "$CLEANUP_REQUEST" ]]; then + log_info "No cleanup request found at $CLEANUP_REQUEST" + exit 0 +fi + +log_info "Processing cleanup request from $CLEANUP_REQUEST" + +# Read and parse the cleanup request +CLEANUP_DATA=$(cat "$CLEANUP_REQUEST") +HOST=$(echo "$CLEANUP_DATA" | grep -o '"host":"[^"]*"' | cut -d'"' -f4 || echo "") +REQUESTED_AT=$(echo "$CLEANUP_DATA" | grep -o '"requestedAt":"[^"]*"' | cut -d'"' -f4 || echo "") + +log_info "Cleanup requested at: ${REQUESTED_AT:-unknown}" + +# Remove the cleanup request file immediately to prevent re-processing +rm -f "$CLEANUP_REQUEST" + +# If no specific host was provided, clean up all known nodes +if [[ -z "$HOST" ]]; then + log_info "No specific host provided - cleaning up all cluster nodes" + + # Discover cluster nodes + if command -v pvecm >/dev/null 2>&1; then + CLUSTER_NODES=$(pvecm status 2>/dev/null | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {print $3}') + + if [[ -n "$CLUSTER_NODES" ]]; then + for node_ip in $CLUSTER_NODES; do + log_info "Cleaning up SSH keys on node $node_ip" + + # Remove both pulse-managed-key and pulse-proxy-key entries + ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 root@"$node_ip" \ + "sed -i -e '/# pulse-managed-key\$/d' -e '/# pulse-proxy-key\$/d' /root/.ssh/authorized_keys" 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on $node_ip" + done + log_info "Cluster cleanup completed" + else + # Standalone node - clean up localhost + log_info "Standalone node detected - cleaning up localhost" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + fi + else + log_warn "pvecm command not available - cleaning up localhost only" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + fi +else + log_info "Cleaning up specific host: $HOST" + + # Extract IP from host URL + HOST_CLEAN=$(echo "$HOST" | sed -e 's|^https\?://||' -e 's|:.*$||') + + # Check if this is localhost + LOCAL_IPS=$(hostname -I 2>/dev/null || echo "") + IS_LOCAL=false + + for local_ip in $LOCAL_IPS; do + if [[ "$HOST_CLEAN" == "$local_ip" ]]; then + IS_LOCAL=true + break + fi + done + + if [[ "$HOST_CLEAN" == "127.0.0.1" || "$HOST_CLEAN" == "localhost" ]]; then + IS_LOCAL=true + fi + + if [[ "$IS_LOCAL" == true ]]; then + log_info "Cleaning up localhost SSH keys" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + else + log_info "Cleaning up remote host: $HOST_CLEAN" + + # Remove both pulse-managed-key and pulse-proxy-key entries from remote host + ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 root@"$HOST_CLEAN" \ + "sed -i -e '/# pulse-managed-key\$/d' -e '/# pulse-proxy-key\$/d' /root/.ssh/authorized_keys" 2>&1 | \ + logger -t "$LOG_TAG" -p user.info + + if [[ $? -eq 0 ]]; then + log_info "Successfully cleaned up SSH keys on $HOST_CLEAN" + else + log_error "Failed to clean up SSH keys on $HOST_CLEAN" + exit 1 + fi + fi +fi + +log_info "Cleanup completed successfully" +exit 0 +CLEANUP_EOF + +chmod +x "$CLEANUP_SCRIPT_PATH" +print_info "Cleanup script installed" + +# Install systemd path unit +CLEANUP_PATH_UNIT="/etc/systemd/system/pulse-sensor-cleanup.path" +cat > "$CLEANUP_PATH_UNIT" << 'PATH_EOF' +[Unit] +Description=Watch for Pulse sensor cleanup requests +Documentation=https://github.com/rcourtman/Pulse + +[Path] +# Watch for the cleanup request file +PathChanged=/var/lib/pulse-sensor-proxy/cleanup-request.json +# Also watch for modifications +PathModified=/var/lib/pulse-sensor-proxy/cleanup-request.json + +[Install] +WantedBy=multi-user.target +PATH_EOF + +# Install systemd service unit +CLEANUP_SERVICE_UNIT="/etc/systemd/system/pulse-sensor-cleanup.service" +cat > "$CLEANUP_SERVICE_UNIT" << 'SERVICE_EOF' +[Unit] +Description=Pulse Sensor Cleanup Service +Documentation=https://github.com/rcourtman/Pulse +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/pulse-sensor-cleanup.sh +User=root +Group=root +WorkingDirectory=/var/lib/pulse-sensor-proxy + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=pulse-sensor-cleanup + +# Security hardening (less restrictive than the proxy since we need SSH access) +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/var/lib/pulse-sensor-proxy /root/.ssh +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +PrivateTmp=true +RestrictSUIDSGID=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +LimitNOFILE=1024 + +[Install] +# This service is triggered by the .path unit, no need to enable it directly +SERVICE_EOF + +# Enable and start the path unit +systemctl daemon-reload +systemctl enable pulse-sensor-cleanup.path +systemctl start pulse-sensor-cleanup.path + +print_info "Cleanup system installed and enabled" + # Configure SSH keys for cluster temperature monitoring print_info "Configuring proxy SSH access to cluster nodes..." diff --git a/scripts/pulse-sensor-cleanup.sh b/scripts/pulse-sensor-cleanup.sh new file mode 100644 index 0000000..23534ad --- /dev/null +++ b/scripts/pulse-sensor-cleanup.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +# pulse-sensor-cleanup.sh - Removes Pulse SSH keys from Proxmox nodes when they're removed from Pulse +# This script is triggered by systemd path unit when cleanup-request.json is created + +set -euo pipefail + +# Configuration +WORK_DIR="/var/lib/pulse-sensor-proxy" +CLEANUP_REQUEST="${WORK_DIR}/cleanup-request.json" +LOG_TAG="pulse-sensor-cleanup" + +# Logging functions +log_info() { + logger -t "$LOG_TAG" -p user.info "$1" + echo "[INFO] $1" +} + +log_warn() { + logger -t "$LOG_TAG" -p user.warning "$1" + echo "[WARN] $1" +} + +log_error() { + logger -t "$LOG_TAG" -p user.err "$1" + echo "[ERROR] $1" >&2 +} + +# Check if cleanup request file exists +if [[ ! -f "$CLEANUP_REQUEST" ]]; then + log_info "No cleanup request found at $CLEANUP_REQUEST" + exit 0 +fi + +log_info "Processing cleanup request from $CLEANUP_REQUEST" + +# Read and parse the cleanup request +CLEANUP_DATA=$(cat "$CLEANUP_REQUEST") +HOST=$(echo "$CLEANUP_DATA" | grep -o '"host":"[^"]*"' | cut -d'"' -f4 || echo "") +REQUESTED_AT=$(echo "$CLEANUP_DATA" | grep -o '"requestedAt":"[^"]*"' | cut -d'"' -f4 || echo "") + +log_info "Cleanup requested at: ${REQUESTED_AT:-unknown}" + +# Remove the cleanup request file immediately to prevent re-processing +rm -f "$CLEANUP_REQUEST" + +# If no specific host was provided, clean up all known nodes +if [[ -z "$HOST" ]]; then + log_info "No specific host provided - cleaning up all cluster nodes" + + # Discover cluster nodes + if command -v pvecm >/dev/null 2>&1; then + CLUSTER_NODES=$(pvecm status 2>/dev/null | awk '/0x[0-9a-f]+.*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ {print $3}') + + if [[ -n "$CLUSTER_NODES" ]]; then + for node_ip in $CLUSTER_NODES; do + log_info "Cleaning up SSH keys on node $node_ip" + + # Remove both pulse-managed-key and pulse-proxy-key entries + ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 root@"$node_ip" \ + "sed -i -e '/# pulse-managed-key\$/d' -e '/# pulse-proxy-key\$/d' /root/.ssh/authorized_keys" 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on $node_ip" + done + log_info "Cluster cleanup completed" + else + # Standalone node - clean up localhost + log_info "Standalone node detected - cleaning up localhost" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + fi + else + log_warn "pvecm command not available - cleaning up localhost only" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + fi +else + log_info "Cleaning up specific host: $HOST" + + # Extract IP from host URL + HOST_CLEAN=$(echo "$HOST" | sed -e 's|^https\?://||' -e 's|:.*$||') + + # Check if this is localhost + LOCAL_IPS=$(hostname -I 2>/dev/null || echo "") + IS_LOCAL=false + + for local_ip in $LOCAL_IPS; do + if [[ "$HOST_CLEAN" == "$local_ip" ]]; then + IS_LOCAL=true + break + fi + done + + if [[ "$HOST_CLEAN" == "127.0.0.1" || "$HOST_CLEAN" == "localhost" ]]; then + IS_LOCAL=true + fi + + if [[ "$IS_LOCAL" == true ]]; then + log_info "Cleaning up localhost SSH keys" + sed -i -e '/# pulse-managed-key$/d' -e '/# pulse-proxy-key$/d' /root/.ssh/authorized_keys 2>&1 | \ + logger -t "$LOG_TAG" -p user.info || \ + log_warn "Failed to clean up SSH keys on localhost" + else + log_info "Cleaning up remote host: $HOST_CLEAN" + + # Remove both pulse-managed-key and pulse-proxy-key entries from remote host + ssh -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5 root@"$HOST_CLEAN" \ + "sed -i -e '/# pulse-managed-key\$/d' -e '/# pulse-proxy-key\$/d' /root/.ssh/authorized_keys" 2>&1 | \ + logger -t "$LOG_TAG" -p user.info + + if [[ $? -eq 0 ]]; then + log_info "Successfully cleaned up SSH keys on $HOST_CLEAN" + else + log_error "Failed to clean up SSH keys on $HOST_CLEAN" + exit 1 + fi + fi +fi + +log_info "Cleanup completed successfully" +exit 0 diff --git a/scripts/systemd/pulse-sensor-cleanup.path b/scripts/systemd/pulse-sensor-cleanup.path new file mode 100644 index 0000000..76fa4a2 --- /dev/null +++ b/scripts/systemd/pulse-sensor-cleanup.path @@ -0,0 +1,12 @@ +[Unit] +Description=Watch for Pulse sensor cleanup requests +Documentation=https://github.com/rcourtman/Pulse + +[Path] +# Watch for the cleanup request file +PathChanged=/var/lib/pulse-sensor-proxy/cleanup-request.json +# Also watch for modifications +PathModified=/var/lib/pulse-sensor-proxy/cleanup-request.json + +[Install] +WantedBy=multi-user.target diff --git a/scripts/systemd/pulse-sensor-cleanup.service b/scripts/systemd/pulse-sensor-cleanup.service new file mode 100644 index 0000000..09cb8aa --- /dev/null +++ b/scripts/systemd/pulse-sensor-cleanup.service @@ -0,0 +1,31 @@ +[Unit] +Description=Pulse Sensor Cleanup Service +Documentation=https://github.com/rcourtman/Pulse +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/pulse-sensor-cleanup.sh +User=root +Group=root +WorkingDirectory=/var/lib/pulse-sensor-proxy + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=pulse-sensor-cleanup + +# Security hardening (less restrictive than the proxy since we need SSH access) +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/var/lib/pulse-sensor-proxy /root/.ssh +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +PrivateTmp=true +RestrictSUIDSGID=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +LimitNOFILE=1024 + +[Install] +# This service is triggered by the .path unit, no need to enable it directly