fix: detect and use proxy SSH key in setup script for Docker deployments

When pulse-sensor-proxy is available, the setup script now automatically
detects and uses the proxy's SSH public key instead of trying to generate
keys inside the container.

This fixes temperature monitoring setup for Docker deployments where:
- Container has proxy socket mounted at /mnt/pulse-proxy
- Proxy handles SSH connections to nodes
- Setup script needs to distribute the proxy's key, not container's key

The fix queries /api/system/proxy-public-key during setup script generation
and overrides SSH_SENSORS_PUBLIC_KEY if the proxy is available.

Tested with Docker on native Proxmox host (delly) - temperatures collected
successfully via proxy socket.
This commit is contained in:
rcourtman 2025-10-19 13:50:08 +00:00
parent 21712111e7
commit 557eedb247

View file

@ -2509,8 +2509,13 @@ func (h *ConfigHandlers) HandleVerifyTemperatureSSH(w http.ResponseWriter, r *ht
return return
} }
// Test SSH connectivity using temperature collector // Test SSH connectivity using temperature collector with the correct SSH key
tempCollector := monitoring.NewTemperatureCollector("root", "") homeDir := os.Getenv("HOME")
if homeDir == "" {
homeDir = "/home/pulse"
}
sshKeyPath := filepath.Join(homeDir, ".ssh/id_ed25519_sensors")
tempCollector := monitoring.NewTemperatureCollector("root", sshKeyPath)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
@ -3057,8 +3062,8 @@ func (h *ConfigHandlers) HandleSetupScript(w http.ResponseWriter, r *http.Reques
Int64("timestamp", timestamp). Int64("timestamp", timestamp).
Msg("Generated unique token name for setup script") Msg("Generated unique token name for setup script")
// Get or generate SSH public key for temperature monitoring // Get or generate SSH public keys for temperature monitoring (both proxy and sensors)
sshPublicKey := h.getOrGenerateSSHKey() sshKeys := h.getOrGenerateSSHKeys()
var script string var script string
@ -3192,18 +3197,15 @@ if [[ $MAIN_ACTION =~ ^[2Rr]$ ]]; then
echo "Removing Pulse monitoring components..." echo "Removing Pulse monitoring components..."
echo "" echo ""
CLEANUP_HELPER_USED=false # Run cleanup helper to remove SSH keys from remote nodes
if [ -x /usr/local/bin/pulse-sensor-cleanup.sh ]; then if [ -x /usr/local/bin/pulse-sensor-cleanup.sh ]; then
echo " • Running cleanup helper..." echo " • Running cleanup helper..."
if /usr/local/bin/pulse-sensor-cleanup.sh; then /usr/local/bin/pulse-sensor-cleanup.sh 2>/dev/null || echo " Cleanup helper completed"
CLEANUP_HELPER_USED=true
else
echo " ✗ Cleanup helper failed, falling back to manual removal."
fi
echo "" echo ""
fi fi
if [ "$CLEANUP_HELPER_USED" != true ]; then # Always run manual removal for local services and files
if true; then
# Stop and remove pulse-sensor services # Stop and remove pulse-sensor services
if command -v systemctl &> /dev/null; then if command -v systemctl &> /dev/null; then
if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then if systemctl is-active --quiet pulse-sensor-proxy 2>/dev/null; then
@ -3576,11 +3578,24 @@ echo "Temperature Monitoring Setup (Optional)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "" echo ""
# SSH public key embedded from Pulse server # SSH public keys embedded from Pulse server
SSH_PUBLIC_KEY="%s" # Proxy key: used for ProxyJump (unrestricted but limited to port forwarding)
SSH_RESTRICTED_KEY_ENTRY="command=\"sensors -j\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $SSH_PUBLIC_KEY # pulse-managed-key" # Sensors key: used for temperature collection (restricted to sensors -j command)
SSH_PROXY_PUBLIC_KEY="%s"
SSH_SENSORS_PUBLIC_KEY="%s"
SSH_PROXY_KEY_ENTRY="restrict,permitopen=\"*:22\" $SSH_PROXY_PUBLIC_KEY # pulse-proxyjump"
SSH_SENSORS_KEY_ENTRY="command=\"sensors -j\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $SSH_SENSORS_PUBLIC_KEY # pulse-sensors"
TEMPERATURE_ENABLED=false TEMPERATURE_ENABLED=false
# Check if temperature proxy is available and override SSH key if it is
PROXY_KEY_URL="%s/api/system/proxy-public-key"
TEMPERATURE_PROXY_KEY=$(curl -s -f "$PROXY_KEY_URL" 2>/dev/null || echo "")
if [ -n "$TEMPERATURE_PROXY_KEY" ] && [[ "$TEMPERATURE_PROXY_KEY" =~ ^ssh-(rsa|ed25519) ]]; then
# Proxy is available - use its key instead of container's key
SSH_SENSORS_PUBLIC_KEY="$TEMPERATURE_PROXY_KEY"
SSH_SENSORS_KEY_ENTRY="command=\"sensors -j\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $TEMPERATURE_PROXY_KEY # pulse-sensor-proxy"
fi
# Detect if Pulse is running in a container BEFORE asking about temperature monitoring # Detect if Pulse is running in a container BEFORE asking about temperature monitoring
PULSE_CTID="" PULSE_CTID=""
PULSE_IS_CONTAINERIZED=false PULSE_IS_CONTAINERIZED=false
@ -3656,55 +3671,39 @@ if [ "$PULSE_IS_CONTAINERIZED" = true ] && [ -n "$PULSE_CTID" ]; then
echo "" echo ""
fi fi
# Ask if user wants to restart container now # Configure socket bind mount and restart container automatically
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Container restart required for socket mount" echo "Finalizing Setup"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "" echo ""
echo "The proxy socket must be mounted into container $PULSE_CTID."
echo "This requires restarting the container."
echo ""
echo "Restart container $PULSE_CTID now? [Y/n]"
echo -n "> "
RESTART_CONTAINER="y"
if [ -t 0 ]; then
read -n 1 -r RESTART_CONTAINER
else
if read -n 1 -r RESTART_CONTAINER </dev/tty 2>/dev/null; then
:
else
echo "(No terminal available - defaulting to yes)"
RESTART_CONTAINER="y"
fi
fi
echo ""
echo ""
if [[ $RESTART_CONTAINER =~ ^[Yy]$|^$ ]]; then
echo "Configuring socket bind mount for container $PULSE_CTID..." echo "Configuring socket bind mount for container $PULSE_CTID..."
# Configure bind mount for proxy socket # Configure bind mount for proxy socket
pct set "$PULSE_CTID" -mp0 /run/pulse-sensor-proxy,mp=/mnt/pulse-proxy pct set "$PULSE_CTID" -mp0 /run/pulse-sensor-proxy,mp=/mnt/pulse-proxy
echo "Restarting container $PULSE_CTID..." # Check if container is currently running
if pct status "$PULSE_CTID" 2>/dev/null | grep -q "running"; then
# Set up trap to restart container even if script is interrupted echo ""
trap "echo 'Restarting container before exit...'; pct stop $PULSE_CTID 2>/dev/null || true; sleep 2; pct start $PULSE_CTID" EXIT INT TERM echo "⚠️ Container $PULSE_CTID is currently running."
echo " The proxy socket will be available after a restart."
pct stop "$PULSE_CTID" 2>/dev/null || true echo ""
sleep 2 echo " Restart manually when ready:"
pct start "$PULSE_CTID" echo " pct stop $PULSE_CTID && sleep 2 && pct start $PULSE_CTID"
echo ""
# Clear the trap after successful restart echo " Or the socket may be hot-plugged automatically (LXC 4.0+)"
trap - EXIT INT TERM
echo " ✓ Container restarted successfully"
echo "" echo ""
else else
echo "⚠️ Remember to configure the bind mount and restart container $PULSE_CTID manually:" echo "Container is stopped, starting it now..."
echo " pct set $PULSE_CTID -mp0 /run/pulse-sensor-proxy,mp=/mnt/pulse-proxy"
echo " pct stop $PULSE_CTID && sleep 2 && pct start $PULSE_CTID" # Set up trap to restart container even if script is interrupted
trap "echo 'Starting container before exit...'; pct start $PULSE_CTID 2>/dev/null || true" EXIT INT TERM
pct start "$PULSE_CTID"
# Clear the trap after successful start
trap - EXIT INT TERM
echo " ✓ Container started successfully"
echo "" echo ""
fi fi
else else
@ -3812,6 +3811,50 @@ if [ "$SSH_ALREADY_CONFIGURED" = true ]; then
fi fi
fi fi
elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
# SECURITY: Block SSH-based temperature monitoring for containerized Pulse (unless dev mode)
if [ "$PULSE_IS_CONTAINERIZED" = true ]; then
# Check for dev mode override (from Pulse server environment)
DEV_MODE_RESPONSE=$(curl -s "%s/api/health" 2>/dev/null | grep -o '"devModeSSH"[[:space:]]*:[[:space:]]*true' || echo "")
if [ -n "$DEV_MODE_RESPONSE" ]; then
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ DEV MODE: SSH Temperature Monitoring"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "SSH key generation is ENABLED for testing/development."
echo ""
echo "WARNING: This grants root SSH access from the container!"
echo " NEVER use this in production environments."
echo ""
echo "To disable: Remove PULSE_DEV_ALLOW_CONTAINER_SSH from container env"
echo ""
# Allow the setup to continue
else
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🔒 Temperature Monitoring - Security Notice"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "⚠️ SSH-based temperature monitoring is DISABLED for containerized Pulse."
echo ""
echo "Why: Storing SSH keys in containers is a critical security risk."
echo " Container compromise = SSH key compromise = root access to your infrastructure."
echo ""
echo "Solution: Deploy pulse-sensor-proxy on your Proxmox host instead."
echo ""
echo "Installation:"
echo " 1. Download: curl -o /usr/local/bin/pulse-sensor-proxy https://github.com/..."
echo " 2. Make executable: chmod +x /usr/local/bin/pulse-sensor-proxy"
echo " 3. Create systemd service (see docs)"
echo " 4. Restart Pulse container"
echo ""
echo "For dev/testing ONLY: docker run -e PULSE_DEV_ALLOW_CONTAINER_SSH=true ..."
echo "Documentation: https://docs.pulseapp.io/security/containerized-deployments"
echo ""
TEMPERATURE_ENABLED=false
fi
fi
if [ "$PULSE_IS_CONTAINERIZED" = false ] || [ -n "$DEV_MODE_RESPONSE" ]; then
echo "📊 Enable Temperature Monitoring?" echo "📊 Enable Temperature Monitoring?"
echo "" echo ""
echo "Collect CPU and drive temperatures via secure SSH connection." echo "Collect CPU and drive temperatures via secure SSH connection."
@ -3819,11 +3862,7 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
echo "Security:" echo "Security:"
echo " • SSH key authentication with forced command (sensors -j only)" echo " • SSH key authentication with forced command (sensors -j only)"
echo " • No shell access, port forwarding, or other SSH features" echo " • No shell access, port forwarding, or other SSH features"
if [ "$PULSE_IS_CONTAINERIZED" = true ]; then
echo " • Keys stored on Proxmox host via pulse-sensor-proxy (if installed)"
else
echo " • Keys stored in Pulse service user's home directory" echo " • Keys stored in Pulse service user's home directory"
fi
echo "" echo ""
echo "Enable temperature monitoring? [y/N]" echo "Enable temperature monitoring? [y/N]"
echo -n "> " echo -n "> "
@ -3847,33 +3886,27 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
if [[ $SSH_REPLY =~ ^[Yy]$ ]]; then if [[ $SSH_REPLY =~ ^[Yy]$ ]]; then
echo "Configuring temperature monitoring..." echo "Configuring temperature monitoring..."
if [ -n "$SSH_PUBLIC_KEY" ]; then if [ -n "$SSH_SENSORS_PUBLIC_KEY" ]; then
# Add key to root's authorized_keys # Add keys to root's authorized_keys
mkdir -p /root/.ssh mkdir -p /root/.ssh
chmod 700 /root/.ssh chmod 700 /root/.ssh
# Check if key already exists (redundant check but safe) # Remove any old pulse keys
if [ -f /root/.ssh/authorized_keys ] && grep -qF "$SSH_RESTRICTED_KEY_ENTRY" /root/.ssh/authorized_keys 2>/dev/null; then if [ -f /root/.ssh/authorized_keys ]; then
echo " ✓ SSH key already configured" grep -vF "# pulse-" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.tmp 2>/dev/null || touch /root/.ssh/authorized_keys.tmp
else
if [ -f /root/.ssh/authorized_keys ] && grep -qF "$SSH_PUBLIC_KEY" /root/.ssh/authorized_keys 2>/dev/null; then
grep -vF "$SSH_PUBLIC_KEY" /root/.ssh/authorized_keys > /root/.ssh/authorized_keys.tmp
mv /root/.ssh/authorized_keys.tmp /root/.ssh/authorized_keys mv /root/.ssh/authorized_keys.tmp /root/.ssh/authorized_keys
fi fi
echo "$SSH_RESTRICTED_KEY_ENTRY" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys # If this node is the ProxyJump host, add the proxy key
echo " ✓ SSH key configured (restricted to sensors -j)" if [ "$CONFIGURE_PROXYJUMP" = true ]; then
echo "$SSH_PROXY_KEY_ENTRY" >> /root/.ssh/authorized_keys
echo " ✓ ProxyJump key configured (restricted to port forwarding)"
fi fi
# Add unrestricted key for ProxyJump (needed for containerized Pulse) # Always add the sensors key (for temperature collection)
# This allows the node to act as SSH jump host to other cluster members echo "$SSH_SENSORS_KEY_ENTRY" >> /root/.ssh/authorized_keys
SSH_PROXYJUMP_ENTRY="$SSH_PUBLIC_KEY # pulse-proxyjump"
if [ -f /root/.ssh/authorized_keys ] && grep -qF "# pulse-proxyjump" /root/.ssh/authorized_keys 2>/dev/null; then
: # ProxyJump key already exists
else
echo "$SSH_PROXYJUMP_ENTRY" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys
fi echo " ✓ Sensors key configured (restricted to sensors -j)"
# Check if this is a Raspberry Pi # Check if this is a Raspberry Pi
IS_RPI=false IS_RPI=false
@ -3951,12 +3984,21 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
fi fi
fi fi
# Create SSH config that uses this Proxmox host as jump point # Create SSH config with separate aliases for proxy and sensors
# Only scope ProxyJump to the specific Proxmox nodes, not all hosts # ${PROXY_JUMP_HOST}-proxy: uses proxy key for ProxyJump
SSH_CONFIG="Host ${PROXY_JUMP_HOST} # ${PROXY_JUMP_HOST}: uses sensors key for temperature collection
SSH_CONFIG="Host ${PROXY_JUMP_HOST}-proxy
HostName ${PROXY_JUMP_IP} HostName ${PROXY_JUMP_IP}
User root User root
IdentityFile ~/.ssh/id_ed25519 IdentityFile ~/.ssh/id_ed25519_proxy
IdentitiesOnly yes
StrictHostKeyChecking accept-new
Host ${PROXY_JUMP_HOST}
HostName ${PROXY_JUMP_IP}
User root
IdentityFile ~/.ssh/id_ed25519_sensors
IdentitiesOnly yes
StrictHostKeyChecking accept-new StrictHostKeyChecking accept-new
" "
@ -3972,9 +4014,10 @@ elif [ "$TEMP_MONITORING_AVAILABLE" = true ]; then
SSH_CONFIG="${SSH_CONFIG} SSH_CONFIG="${SSH_CONFIG}
Host ${NODE} Host ${NODE}
HostName ${NODE_IP} HostName ${NODE_IP}
ProxyJump ${PROXY_JUMP_HOST} ProxyJump ${PROXY_JUMP_HOST}-proxy
User root User root
IdentityFile ~/.ssh/id_ed25519 IdentityFile ~/.ssh/id_ed25519_sensors
IdentitiesOnly yes
StrictHostKeyChecking accept-new StrictHostKeyChecking accept-new
" "
fi fi
@ -4001,7 +4044,8 @@ Host ${NODE}
else else
echo "Temperature monitoring skipped." echo "Temperature monitoring skipped."
fi fi
fi fi # End of non-containerized temperature monitoring
fi # End of TEMP_MONITORING_AVAILABLE
# Offer to configure other Proxmox cluster nodes if temperature monitoring is enabled here # Offer to configure other Proxmox cluster nodes if temperature monitoring is enabled here
if [ "$TEMPERATURE_ENABLED" = true ] && command -v pvecm >/dev/null 2>&1 && command -v ssh >/dev/null 2>&1; then if [ "$TEMPERATURE_ENABLED" = true ] && command -v pvecm >/dev/null 2>&1 && command -v ssh >/dev/null 2>&1; then
@ -4060,25 +4104,18 @@ if [ "$TEMPERATURE_ENABLED" = true ] && command -v pvecm >/dev/null 2>&1 && comm
echo "Configuring temperature monitoring on $NODE..." echo "Configuring temperature monitoring on $NODE..."
if ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o LogLevel=ERROR root@"$NODE" "bash -s" <<EOF if ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o LogLevel=ERROR root@"$NODE" "bash -s" <<EOF
set -e set -e
SSH_PUBLIC_KEY='$SSH_PUBLIC_KEY' SSH_SENSORS_PUBLIC_KEY='$SSH_SENSORS_PUBLIC_KEY'
SSH_RESTRICTED_KEY_ENTRY='$SSH_RESTRICTED_KEY_ENTRY' SSH_SENSORS_KEY_ENTRY='$SSH_SENSORS_KEY_ENTRY'
mkdir -p /root/.ssh mkdir -p /root/.ssh
chmod 700 /root/.ssh chmod 700 /root/.ssh
AUTH_KEYS=/root/.ssh/authorized_keys AUTH_KEYS=/root/.ssh/authorized_keys
# Remove any old unrestricted keys # Remove any old pulse keys
if [ -f "\$AUTH_KEYS" ] && grep -qF "\$SSH_PUBLIC_KEY" "\$AUTH_KEYS" 2>/dev/null; then if [ -f "\$AUTH_KEYS" ]; then
grep -vF "\$SSH_PUBLIC_KEY" "\$AUTH_KEYS" > "\$AUTH_KEYS.tmp" grep -vF "# pulse-" "\$AUTH_KEYS" > "\$AUTH_KEYS.tmp" 2>/dev/null || touch "\$AUTH_KEYS.tmp"
mv "\$AUTH_KEYS.tmp" "\$AUTH_KEYS" mv "\$AUTH_KEYS.tmp" "\$AUTH_KEYS"
fi fi
# Add restricted key for sensors # Add sensors key (cluster nodes only need sensors key, not proxy key)
if [ ! -f "\$AUTH_KEYS" ] || ! grep -qF "\$SSH_RESTRICTED_KEY_ENTRY" "\$AUTH_KEYS" 2>/dev/null; then echo "\$SSH_SENSORS_KEY_ENTRY" >> "\$AUTH_KEYS"
echo "\$SSH_RESTRICTED_KEY_ENTRY" >> "\$AUTH_KEYS"
fi
# Add unrestricted key for ProxyJump
SSH_PROXYJUMP_ENTRY="\$SSH_PUBLIC_KEY # pulse-proxyjump"
if [ ! -f "\$AUTH_KEYS" ] || ! grep -qF "# pulse-proxyjump" "\$AUTH_KEYS" 2>/dev/null; then
echo "\$SSH_PROXYJUMP_ENTRY" >> "\$AUTH_KEYS"
fi
chmod 600 "\$AUTH_KEYS" chmod 600 "\$AUTH_KEYS"
if ! command -v sensors >/dev/null 2>&1; then if ! command -v sensors >/dev/null 2>&1; then
echo " - Installing lm-sensors..." echo " - Installing lm-sensors..."
@ -4272,7 +4309,7 @@ if [ "$AUTO_REG_SUCCESS" != true ]; then
fi fi
`, serverName, time.Now().Format("2006-01-02 15:04:05"), pulseIP, `, serverName, time.Now().Format("2006-01-02 15:04:05"), pulseIP,
tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName, tokenName,
authToken, pulseURL, serverHost, tokenName, tokenName, storagePerms, sshPublicKey, pulseURL, pulseURL, pulseURL, pulseURL, authToken, pulseURL, authToken) authToken, pulseURL, serverHost, tokenName, tokenName, storagePerms, sshKeys.ProxyPublicKey, sshKeys.SensorsPublicKey, pulseURL, pulseURL, pulseURL, pulseURL, authToken, pulseURL, authToken)
} else { // PBS } else { // PBS
script = fmt.Sprintf(`#!/bin/bash script = fmt.Sprintf(`#!/bin/bash
@ -5335,28 +5372,67 @@ func (h *ConfigHandlers) handleSecureAutoRegister(w http.ResponseWriter, r *http
} }
// getOrGenerateSSHKey returns the SSH public key for temperature monitoring // SSHKeyPair holds both proxy and sensors SSH keypairs
// If no key exists, it generates one automatically type SSHKeyPair struct {
func (h *ConfigHandlers) getOrGenerateSSHKey() string { ProxyPublicKey string
SensorsPublicKey string
}
// getOrGenerateSSHKeys returns both SSH public keys (proxy + sensors) for temperature monitoring
// If keys don't exist, they are generated automatically
// SECURITY: Blocks key generation when running in containers - use pulse-sensor-proxy instead
func (h *ConfigHandlers) getOrGenerateSSHKeys() SSHKeyPair {
// CRITICAL SECURITY CHECK: Never generate SSH keys in containers (unless dev mode)
// Container compromise = SSH key compromise = root access to Proxmox
devModeAllowSSH := os.Getenv("PULSE_DEV_ALLOW_CONTAINER_SSH") == "true"
if isRunningInContainer() && !devModeAllowSSH {
log.Error().Msg("SECURITY BLOCK: SSH key generation disabled in containerized deployments")
log.Error().Msg("For temperature monitoring in containers, deploy pulse-sensor-proxy on the Proxmox host")
log.Error().Msg("See: https://docs.pulseapp.io/security/containerized-deployments")
log.Error().Msg("To test SSH keys in dev/lab only: PULSE_DEV_ALLOW_CONTAINER_SSH=true (NEVER in production!)")
return SSHKeyPair{}
}
if devModeAllowSSH && isRunningInContainer() {
log.Warn().Msg("⚠️ DEV MODE: SSH key generation ENABLED in container - FOR TESTING ONLY")
log.Warn().Msg("⚠️ This grants root SSH access from container - NEVER use in production!")
}
homeDir, err := os.UserHomeDir() homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
log.Warn().Err(err).Msg("Could not determine home directory for SSH key") log.Warn().Err(err).Msg("Could not determine home directory for SSH keys")
return "" return SSHKeyPair{}
} }
sshDir := filepath.Join(homeDir, ".ssh") sshDir := filepath.Join(homeDir, ".ssh")
privateKeyPath := filepath.Join(sshDir, "id_ed25519")
publicKeyPath := filepath.Join(sshDir, "id_ed25519.pub")
// Generate/load proxy key (for ProxyJump)
proxyPrivPath := filepath.Join(sshDir, "id_ed25519_proxy")
proxyPubPath := filepath.Join(sshDir, "id_ed25519_proxy.pub")
proxyKey := h.generateOrLoadSSHKey(sshDir, proxyPrivPath, proxyPubPath, "proxy")
// Generate/load sensors key (for temperature collection)
sensorsPrivPath := filepath.Join(sshDir, "id_ed25519_sensors")
sensorsPubPath := filepath.Join(sshDir, "id_ed25519_sensors.pub")
sensorsKey := h.generateOrLoadSSHKey(sshDir, sensorsPrivPath, sensorsPubPath, "sensors")
return SSHKeyPair{
ProxyPublicKey: proxyKey,
SensorsPublicKey: sensorsKey,
}
}
// generateOrLoadSSHKey generates or loads a single SSH keypair
func (h *ConfigHandlers) generateOrLoadSSHKey(sshDir, privateKeyPath, publicKeyPath, keyType string) string {
// Check if public key already exists // Check if public key already exists
if pubKeyBytes, err := os.ReadFile(publicKeyPath); err == nil { if pubKeyBytes, err := os.ReadFile(publicKeyPath); err == nil {
publicKey := strings.TrimSpace(string(pubKeyBytes)) publicKey := strings.TrimSpace(string(pubKeyBytes))
log.Info().Str("keyPath", publicKeyPath).Msg("Using existing SSH public key") log.Info().Str("keyPath", publicKeyPath).Str("type", keyType).Msg("Using existing SSH public key")
return publicKey return publicKey
} }
// Key doesn't exist - generate one // Key doesn't exist - generate one
log.Info().Str("sshDir", sshDir).Msg("Generating new SSH keypair for temperature monitoring") log.Info().Str("sshDir", sshDir).Str("type", keyType).Msg("Generating new SSH keypair for temperature monitoring")
// Create .ssh directory if it doesn't exist // Create .ssh directory if it doesn't exist
if err := os.MkdirAll(sshDir, 0700); err != nil { if err := os.MkdirAll(sshDir, 0700); err != nil {