Fix cluster proxy token collision - store per-node control tokens

When multiple cluster nodes register sensor-proxy, each registration
was overwriting the previous node's control token on the shared
PVEInstance. This caused "Proxy token not recognized" errors on all
but the last-registered node.

Changes:
- Add TemperatureProxyControlToken field to ClusterEndpoint struct
- Store control tokens per-endpoint for cluster registrations
- Check both instance-level and endpoint-level tokens when validating

Related to #738
This commit is contained in:
rcourtman 2025-11-30 20:37:58 +00:00
parent db56cb7d0d
commit f8c7a30d28
2 changed files with 36 additions and 2 deletions

View file

@ -230,6 +230,7 @@ func (h *TemperatureProxyHandlers) HandleRegister(w http.ResponseWriter, r *http
// Find matching PVE instance by hostname
var matchedInstance *config.PVEInstance
var matchedIndex int
var matchedEndpointIndex = -1 // For cluster nodes, track which endpoint matched
for i := range nodesConfig.PVEInstances {
instance := &nodesConfig.PVEInstances[i]
@ -250,10 +251,11 @@ func (h *TemperatureProxyHandlers) HandleRegister(w http.ResponseWriter, r *http
// Check cluster endpoints
if instance.IsCluster {
for _, ep := range instance.ClusterEndpoints {
for j, ep := range instance.ClusterEndpoints {
if strings.EqualFold(ep.NodeName, hostname) || strings.Contains(strings.ToLower(ep.Host), strings.ToLower(hostname)) {
matchedInstance = instance
matchedIndex = i
matchedEndpointIndex = j
break
}
}
@ -282,15 +284,28 @@ func (h *TemperatureProxyHandlers) HandleRegister(w http.ResponseWriter, r *http
}
// Update the instance with proxy configuration
// For clusters, store per-node tokens on the ClusterEndpoint; instance-level token is for non-cluster or fallback
nodesConfig.PVEInstances[matchedIndex].TemperatureProxyURL = proxyURL
if isHTTPMode {
nodesConfig.PVEInstances[matchedIndex].TemperatureProxyToken = authToken
}
nodesConfig.PVEInstances[matchedIndex].TemperatureProxyControlToken = ctrlToken
// For cluster nodes, store token on the specific endpoint so each node has its own token
if matchedEndpointIndex >= 0 && matchedInstance.IsCluster {
nodesConfig.PVEInstances[matchedIndex].ClusterEndpoints[matchedEndpointIndex].TemperatureProxyControlToken = ctrlToken
log.Debug().
Str("hostname", hostname).
Int("endpoint_index", matchedEndpointIndex).
Msg("Storing control token on cluster endpoint")
} else {
// Non-cluster instance or matched by instance name directly
nodesConfig.PVEInstances[matchedIndex].TemperatureProxyControlToken = ctrlToken
}
// Save updated configuration
log.Debug().
Int("matchedIndex", matchedIndex).
Int("matchedEndpointIndex", matchedEndpointIndex).
Str("saving_url", nodesConfig.PVEInstances[matchedIndex].TemperatureProxyURL).
Bool("saving_has_token", nodesConfig.PVEInstances[matchedIndex].TemperatureProxyToken != "").
Str("instance_name", nodesConfig.PVEInstances[matchedIndex].Name).
@ -368,6 +383,8 @@ func (h *TemperatureProxyHandlers) HandleAuthorizedNodes(w http.ResponseWriter,
var matched *config.PVEInstance
for i := range nodesConfig.PVEInstances {
inst := &nodesConfig.PVEInstances[i]
// Check instance-level token first
switch {
case strings.TrimSpace(inst.TemperatureProxyControlToken) == token:
matched = inst
@ -378,6 +395,20 @@ func (h *TemperatureProxyHandlers) HandleAuthorizedNodes(w http.ResponseWriter,
if matched != nil {
break
}
// For clusters, also check per-node tokens on ClusterEndpoints
if inst.IsCluster {
for j := range inst.ClusterEndpoints {
ep := &inst.ClusterEndpoints[j]
if strings.TrimSpace(ep.TemperatureProxyControlToken) == token {
matched = inst
break
}
}
if matched != nil {
break
}
}
}
refreshFromProxy := 0

View file

@ -438,6 +438,9 @@ type ClusterEndpoint struct {
PulseReachable *bool // Pulse's view: can Pulse reach this endpoint? nil = not yet checked
LastPulseCheck *time.Time // Last time Pulse checked connectivity
PulseError string // Last error Pulse encountered connecting to this endpoint
// Per-node temperature proxy tokens (for clusters with sensor-proxy on each node)
TemperatureProxyControlToken string // Control-plane token for this specific node
}
// PBSInstance represents a Proxmox Backup Server connection