fix(configapi): preserve operator VerifySSL on auto-register

Re-registration of an existing node overwrote the stored VerifySSL value with the fingerprint-capture result, silently re-enabling a disabled Verify SSL certificate setting on every agent health-check repair (#2140). Preserve the operator's choice and keep only the legacy heal where strict verification is on with no pin (#1303).

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot] 2026-09-21 11:26:23 +01:00
parent c008d33d40
commit 529e815525
2 changed files with 119 additions and 2 deletions

View file

@ -1557,3 +1557,102 @@ func TestHandleCanonicalAutoRegister_PBSKeepsDistinctSameNameTokenWhenFingerprin
t.Fatalf("new site name = %q, want disambiguation from existing %q", registered.Name, existing.Name)
}
}
// TestHandleCanonicalAutoRegister_PVEPreservesDisabledVerifySSL covers #2140:
// an operator who disables "Verify SSL certificate" on an existing node must
// not have it silently re-enabled by an agent health-check re-registration.
func TestHandleCanonicalAutoRegister_PVEPreservesDisabledVerifySSL(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", tempDir)
server := newIPv4TLSServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
tokenID := "pulse-monitor@pve!" + buildPulseMonitorTokenName("pulse.example.com")
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
PVEInstances: []config.PVEInstance{
{
Name: "pve01",
Host: server.URL,
TokenName: tokenID,
TokenValue: "existing-token",
VerifySSL: false,
},
},
}
handler := newTestConfigHandlers(t, cfg)
reqBody := AutoRegisterRequest{
Type: "pve",
Host: server.URL,
ServerName: "pve01",
TokenID: tokenID,
TokenValue: "rotated-token",
Source: "agent",
}
req := httptest.NewRequest(http.MethodPost, "/api/auto-register", nil)
rec := httptest.NewRecorder()
handler.handleCanonicalAutoRegister(rec, req, &reqBody, "127.0.0.1")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
}
instance := handler.defaultConfig.PVEInstances[0]
if instance.VerifySSL {
t.Fatalf("re-registration re-enabled VerifySSL on an existing node that disabled it (#2140)")
}
}
// TestHandleCanonicalAutoRegister_PBSReservesDisabledVerifySSL is the PBS twin
// of TestHandleCanonicalAutoRegister_PVEPreservesDisabledVerifySSL.
func TestHandleCanonicalAutoRegister_PBSReservesDisabledVerifySSL(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", tempDir)
server := newIPv4TLSServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()
tokenID := "pulse-monitor@pbs!" + buildPulseMonitorTokenName("pulse.example.com")
cfg := &config.Config{
DataPath: tempDir,
ConfigPath: tempDir,
PBSInstances: []config.PBSInstance{
{
Name: "pbs01",
Host: server.URL,
TokenName: tokenID,
TokenValue: "existing-token",
VerifySSL: false,
},
},
}
handler := newTestConfigHandlers(t, cfg)
reqBody := AutoRegisterRequest{
Type: "pbs",
Host: server.URL,
ServerName: "pbs01",
TokenID: tokenID,
TokenValue: "rotated-token",
Source: "agent",
}
req := httptest.NewRequest(http.MethodPost, "/api/auto-register", nil)
rec := httptest.NewRecorder()
handler.handleCanonicalAutoRegister(rec, req, &reqBody, "127.0.0.1")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200: %s", rec.Code, rec.Body.String())
}
instance := handler.defaultConfig.PBSInstances[0]
if instance.VerifySSL {
t.Fatalf("re-registration re-enabled VerifySSL on an existing node that disabled it (#2140)")
}
}

View file

@ -2265,7 +2265,16 @@ func (h *ConfigHandlers) handleCanonicalAutoRegister(w http.ResponseWriter, r *h
if pveNode.Fingerprint != "" {
instance.Fingerprint = pveNode.Fingerprint
}
instance.VerifySSL = pveNode.VerifySSL
// Preserve the operator's TLS choice on an existing connection.
// The incoming value records only whether this registration
// captured a fingerprint; applying it unconditionally re-enabled a
// disabled "Verify SSL certificate" setting on every agent
// health-check re-registration (#2140). Heal only the legacy state
// where strict verification is on with no pin, which can never
// connect to a self-signed endpoint (#1303).
if instance.VerifySSL && instance.Fingerprint == "" {
instance.VerifySSL = false
}
log.Info().Str("host", host).Str("type", "pve").Msg(canonicalAutoRegisterMatchMessage("host; updated token in-place"))
} else if h.adoptCanonicalAutoRegisterClusterMember(r.Context(), serverName, host, fingerprint, fullTokenID, tokenValue, candidateHosts, registrationSource) {
// A non-primary cluster member: the cluster connection already
@ -2319,7 +2328,16 @@ func (h *ConfigHandlers) handleCanonicalAutoRegister(w http.ResponseWriter, r *h
if pbsNode.Fingerprint != "" {
instance.Fingerprint = pbsNode.Fingerprint
}
instance.VerifySSL = pbsNode.VerifySSL
// Preserve the operator's TLS choice on an existing connection.
// The incoming value records only whether this registration
// captured a fingerprint; applying it unconditionally re-enabled a
// disabled "Verify SSL certificate" setting on every agent
// health-check re-registration (#2140). Heal only the legacy state
// where strict verification is on with no pin, which can never
// connect to a self-signed endpoint (#1303).
if instance.VerifySSL && instance.Fingerprint == "" {
instance.VerifySSL = false
}
log.Info().Str("host", host).Str("type", "pbs").Msg(canonicalAutoRegisterMatchMessage("host; updated token in-place"))
} else {
// Agent-token auth is restricted to updating existing nodes only.