From 71217dfae4906bf4078e45dc79e37bb45ad41f96 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 13 Nov 2025 16:33:12 +0000 Subject: [PATCH] Add HTTP mode support to sensor-proxy installer Implements complete HTTP mode installation workflow for external PVE hosts. New installer features: - `--http-mode` flag: Enable HTTP server mode for remote temperature monitoring - `--http-addr ` flag: Configure listen address (default :8443) - Auto-generates self-signed TLS certificates (4096-bit RSA, 10-year validity) - Registers with Pulse API and receives authentication token - Configures systemd service with proper security hardening Installation workflow (HTTP mode): 1. Validate --pulse-server parameter is provided 2. Generate TLS certificate with SAN (hostname + IPs) 3. Call Pulse API POST /api/temperature-proxy/register 4. Receive and store auth token securely (mode 600) 5. Append HTTP config to config.yaml 6. Update systemd service with TLS paths 7. Start service TLS certificate generation: - Uses openssl req with RSA 4096-bit keys - 10-year validity period - SubjectAltName includes hostname + all IPs - Files stored in /etc/pulse-sensor-proxy/tls/ - Permissions: 640 root:pulse-sensor-proxy - Logs SHA256 fingerprint for audit API registration: - Calls POST /api/temperature-proxy/register - Payload: {"hostname": "...", "proxy_url": "https://..."} - Response: {"token": "...", "pve_instance": "..."} - Aborts installation on registration failure (fail-fast) - Token stored in config.yaml Systemd service updates: - Adds ReadOnlyPaths=/etc/pulse-sensor-proxy/tls for HTTP mode - RestrictAddressFamilies already includes AF_INET/AF_INET6 - Maintains all existing security hardening Error handling: - Validates required parameters before starting - Aborts on TLS generation failure - Aborts on API registration failure - Provides actionable troubleshooting guidance - Logs clear error messages Security: - Tokens stored with mode 600, owned by service user - TLS keys protected with mode 640 - Service runs as unprivileged pulse-sensor-proxy user - Full systemd hardening maintained Usage example: curl -fsSL https://pulse-server/download/install-sensor-proxy.sh | \ bash -s -- --http-mode --pulse-server https://pulse.example.com:7655 Related to #571 --- scripts/install-sensor-proxy.sh | 177 ++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/scripts/install-sensor-proxy.sh b/scripts/install-sensor-proxy.sh index e455b8568..69418c9f3 100755 --- a/scripts/install-sensor-proxy.sh +++ b/scripts/install-sensor-proxy.sh @@ -289,6 +289,8 @@ LOCAL_BINARY="" QUIET=false PULSE_SERVER="" STANDALONE=false +HTTP_MODE=false +HTTP_ADDR=":8443" FALLBACK_BASE="${PULSE_SENSOR_PROXY_FALLBACK_URL:-}" SKIP_RESTART=false UNINSTALL=false @@ -320,6 +322,14 @@ while [[ $# -gt 0 ]]; do STANDALONE=true shift ;; + --http-mode) + HTTP_MODE=true + shift + ;; + --http-addr) + HTTP_ADDR="$2" + shift 2 + ;; --skip-restart) SKIP_RESTART=true shift @@ -658,6 +668,112 @@ if [[ -n "$CTID" ]]; then chmod 0644 "$CTID_FILE" fi +# HTTP Mode Setup Functions +setup_tls_certificates() { + local cert_path="$1" + local key_path="$2" + + # Create TLS directory + install -d -o root -g pulse-sensor-proxy -m 0750 /etc/pulse-sensor-proxy/tls + + if [[ -n "$cert_path" && -n "$key_path" ]]; then + # Use provided certificates + print_info "Using provided TLS certificates..." + cp "$cert_path" /etc/pulse-sensor-proxy/tls/server.crt + cp "$key_path" /etc/pulse-sensor-proxy/tls/server.key + chmod 640 /etc/pulse-sensor-proxy/tls/server.crt + chmod 640 /etc/pulse-sensor-proxy/tls/server.key + chown root:pulse-sensor-proxy /etc/pulse-sensor-proxy/tls/server.crt + chown root:pulse-sensor-proxy /etc/pulse-sensor-proxy/tls/server.key + else + # Generate self-signed certificate + print_info "Generating self-signed TLS certificate..." + + # Get hostname and IPs for SAN + HOSTNAME=$(hostname -f 2>/dev/null || hostname) + IP_ADDRESSES=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -v '^$' | head -5) + + # Build SAN list + SAN="DNS:${HOSTNAME},DNS:localhost" + for ip in $IP_ADDRESSES; do + SAN="${SAN},IP:${ip}" + done + + # Generate 4096-bit RSA key and self-signed cert valid for 10 years + openssl req -newkey rsa:4096 -nodes -x509 -days 3650 \ + -subj "/CN=${HOSTNAME}/O=Pulse Sensor Proxy" \ + -addext "subjectAltName=${SAN}" \ + -keyout /etc/pulse-sensor-proxy/tls/server.key \ + -out /etc/pulse-sensor-proxy/tls/server.crt \ + 2>/dev/null || { + print_error "Failed to generate TLS certificate" + exit 1 + } + + chmod 640 /etc/pulse-sensor-proxy/tls/server.key + chmod 640 /etc/pulse-sensor-proxy/tls/server.crt + chown root:pulse-sensor-proxy /etc/pulse-sensor-proxy/tls/server.key + chown root:pulse-sensor-proxy /etc/pulse-sensor-proxy/tls/server.crt + + # Log certificate fingerprint for audit + CERT_FINGERPRINT=$(openssl x509 -in /etc/pulse-sensor-proxy/tls/server.crt -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2) + print_success "TLS certificate generated (SHA256: ${CERT_FINGERPRINT})" + fi +} + +register_with_pulse() { + local pulse_url="$1" + local hostname="$2" + local proxy_url="$3" + + print_info "Registering temperature proxy with Pulse at $pulse_url..." + + # Build registration request + local response + response=$(curl -f -s -X POST \ + -H "Content-Type: application/json" \ + -d "{\"hostname\":\"${hostname}\",\"proxy_url\":\"${proxy_url}\"}" \ + "${pulse_url}/api/temperature-proxy/register" 2>&1) + + if [[ $? -ne 0 ]]; then + print_error "Failed to register with Pulse API" + print_error "Response: $response" + print_error "" + print_error "Troubleshooting:" + print_error " 1. Ensure Pulse server is running at $pulse_url" + print_error " 2. Ensure this PVE instance is already added to Pulse" + print_error " 3. Check hostname matches: $hostname" + print_error " 4. Verify firewall allows access to Pulse" + return 1 + fi + + # Parse token from response + local token + token=$(echo "$response" | grep -o '"token":"[^"]*"' | cut -d'"' -f4) + + if [[ -z "$token" ]]; then + print_error "Registration succeeded but no token received" + print_error "Response: $response" + return 1 + fi + + # Store token + echo "$token" > /etc/pulse-sensor-proxy/.http-auth-token + chmod 600 /etc/pulse-sensor-proxy/.http-auth-token + chown pulse-sensor-proxy:pulse-sensor-proxy /etc/pulse-sensor-proxy/.http-auth-token + + print_success "Registered successfully - token received" + + # Parse instance name from response for logging + local instance_name + instance_name=$(echo "$response" | grep -o '"pve_instance":"[^"]*"' | cut -d'"' -f4) + if [[ -n "$instance_name" ]]; then + print_info "Linked to PVE instance: $instance_name" + fi + + echo "$token" +} + # Create config file with ACL for Docker containers (standalone mode) if [[ "$STANDALONE" == true ]]; then print_info "Creating config file with Docker container ACL..." @@ -675,6 +791,57 @@ EOF chmod 0644 /etc/pulse-sensor-proxy/config.yaml fi +# HTTP Mode Configuration +if [[ "$HTTP_MODE" == true ]]; then + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo " HTTP Mode Setup (External PVE Host)" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + + # Validate required parameters + if [[ -z "$PULSE_SERVER" ]]; then + print_error "HTTP mode requires --pulse-server parameter" + print_error "Example: --pulse-server https://pulse.example.com:7655" + exit 1 + fi + + # Setup TLS certificates + setup_tls_certificates "" "" # Empty params = auto-generate + + # Determine proxy URL + PROXY_HOSTNAME=$(hostname -f 2>/dev/null || hostname) + PROXY_URL="https://${PROXY_HOSTNAME}${HTTP_ADDR}" + print_info "Proxy will be accessible at: $PROXY_URL" + + # Register with Pulse and get auth token + HTTP_AUTH_TOKEN=$(register_with_pulse "$PULSE_SERVER" "$PROXY_HOSTNAME" "$PROXY_URL") + if [[ $? -ne 0 || -z "$HTTP_AUTH_TOKEN" ]]; then + print_error "Failed to register with Pulse - aborting installation" + print_error "Fix the issue and re-run the installer" + exit 1 + fi + + # Append HTTP configuration to config.yaml + print_info "Configuring HTTP mode..." + cat >> /etc/pulse-sensor-proxy/config.yaml << EOF + +# HTTP Mode Configuration (External PVE Host) +http_enabled: true +http_listen_addr: "$HTTP_ADDR" +http_tls_cert: /etc/pulse-sensor-proxy/tls/server.crt +http_tls_key: /etc/pulse-sensor-proxy/tls/server.key +http_auth_token: "$HTTP_AUTH_TOKEN" +EOF + + print_success "HTTP mode configured successfully" + echo "" + print_info "Firewall configuration required:" + print_info " Allow inbound TCP connections on port ${HTTP_ADDR#:} from Pulse server" + print_info " Command: ufw allow from to any port ${HTTP_ADDR#:}" + echo "" +fi + # Stop existing service if running (for upgrades) if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then print_info "Stopping existing service for upgrade..." @@ -805,6 +972,16 @@ WantedBy=multi-user.target EOF fi +# Add HTTP mode paths to service file if needed +if [[ "$HTTP_MODE" == true ]]; then + print_info "Updating service file for HTTP mode..." + + # Add ReadOnlyPaths for TLS directory (after ReadWritePaths line) + sed -i '/^ReadWritePaths=\/var\/lib\/pulse-sensor-proxy/a ReadOnlyPaths=/etc/pulse-sensor-proxy/tls' "$SERVICE_PATH" + + print_success "Service file updated for HTTP mode" +fi + # Reload systemd and start service print_info "Enabling and starting service..." if ! systemctl daemon-reload; then