mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
fix(agent): warn when the server overrides a presented agent ID
Host identity continuity deliberately keeps a known machine on its enrolled ID, so a custom --agent-id or hand-edited agent-id state file is silently ignored for an already-known host, and the acknowledged ID then overwrites the state file within one report cycle. That looked like corruption from the operator's seat. Warn once per resolved identity with both IDs and the supported path to a fresh enrollment (remove the host in Pulse first). Refs #1739 Contract-Neutral: diagnostic logging only in hostagent report ack path, refs #1739
This commit is contained in:
@@ -181,6 +181,11 @@ type Agent struct {
|
||||
availability *availabilityProbeModule
|
||||
reportStreamID string
|
||||
reportSequence atomic.Uint64
|
||||
// canonicalIDWarned holds the last server-acknowledged agent ID a
|
||||
// mismatch warning was emitted for, so the warning fires once per
|
||||
// resolved identity instead of on every report cycle. Only the
|
||||
// report/enroll ack paths touch it, which run sequentially.
|
||||
canonicalIDWarned string
|
||||
// libreHardwareMonitorEndpoint is an unexported test seam. Production
|
||||
// collection always uses the fixed loopback URL when this is empty.
|
||||
libreHardwareMonitorEndpoint string
|
||||
@@ -1372,6 +1377,7 @@ func (a *Agent) sendReportToDestination(ctx context.Context, report agentshost.R
|
||||
// Persist the server-acknowledged agent ID so uninstall can deregister.
|
||||
canonicalAgentID := strings.TrimSpace(reportResp.AgentID)
|
||||
if canonicalAgentID != "" {
|
||||
a.warnCanonicalIDMismatch(canonicalAgentID)
|
||||
a.persistAgentID(canonicalAgentID)
|
||||
}
|
||||
|
||||
@@ -1383,6 +1389,24 @@ func (a *Agent) sendReportToDestination(ctx context.Context, report agentshost.R
|
||||
return nil
|
||||
}
|
||||
|
||||
// warnCanonicalIDMismatch surfaces the case where the server resolved this
|
||||
// agent to a different identity than the one it presented. Host identity
|
||||
// continuity deliberately keeps a known machine on its enrolled ID even when
|
||||
// the operator passes --agent-id or hand-edits the agent-id state file
|
||||
// (#1739), and the acknowledged ID then overwrites that file for uninstall
|
||||
// deregistration. Without this warning the override is ignored silently and
|
||||
// the file rewrite looks like corruption. Warns once per resolved identity.
|
||||
func (a *Agent) warnCanonicalIDMismatch(canonicalAgentID string) {
|
||||
if canonicalAgentID == a.agentID || canonicalAgentID == a.canonicalIDWarned {
|
||||
return
|
||||
}
|
||||
a.canonicalIDWarned = canonicalAgentID
|
||||
a.logger.Warn().
|
||||
Str("presented_agent_id", a.agentID).
|
||||
Str("server_agent_id", canonicalAgentID).
|
||||
Msg("Server resolved this agent to its enrolled identity and the presented agent ID was not adopted. Host identity continuity keeps a known machine on its existing ID. To enroll under a new ID, remove the host in Pulse first, then restart the agent.")
|
||||
}
|
||||
|
||||
// persistAgentID writes the server-assigned agent ID to the state directory.
|
||||
// This file is read by the uninstall script to deregister the agent from the server.
|
||||
// Errors are debug-logged, never fatal — same resilience pattern as proxmox_setup.go.
|
||||
|
||||
@@ -142,3 +142,38 @@ func TestPersistAgentID_MkdirAllPermissions(t *testing.T) {
|
||||
t.Fatalf("MkdirAll perm = %o, want %o", gotPerm, 0700)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarnCanonicalIDMismatch_WarnsOncePerResolvedIdentity(t *testing.T) {
|
||||
var logs []string
|
||||
sink := zerolog.New(writerFunc(func(p []byte) (int, error) {
|
||||
logs = append(logs, string(p))
|
||||
return len(p), nil
|
||||
}))
|
||||
|
||||
a := &Agent{
|
||||
logger: sink,
|
||||
agentID: "custom-id",
|
||||
}
|
||||
|
||||
a.warnCanonicalIDMismatch("machine-id-1")
|
||||
a.warnCanonicalIDMismatch("machine-id-1")
|
||||
if len(logs) != 1 {
|
||||
t.Fatalf("expected 1 warning for repeated identical mismatch, got %d", len(logs))
|
||||
}
|
||||
|
||||
a.warnCanonicalIDMismatch("machine-id-2")
|
||||
if len(logs) != 2 {
|
||||
t.Fatalf("expected a new warning for a different resolved identity, got %d", len(logs))
|
||||
}
|
||||
|
||||
// Matching identity never warns and resets nothing.
|
||||
a.agentID = "machine-id-2"
|
||||
a.warnCanonicalIDMismatch("machine-id-2")
|
||||
if len(logs) != 2 {
|
||||
t.Fatalf("expected no warning when acknowledged ID matches, got %d", len(logs))
|
||||
}
|
||||
}
|
||||
|
||||
type writerFunc func(p []byte) (int, error)
|
||||
|
||||
func (w writerFunc) Write(p []byte) (int, error) { return w(p) }
|
||||
|
||||
Reference in New Issue
Block a user