From 8dc771f09b808592da5575a0233d2a8e4effee75 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 16 Aug 2026 21:54:17 +0100 Subject: [PATCH] Keep default monitor token inventory current --- .../v6/internal/subsystems/monitoring.md | 7 ++++ .../monitoring/canonical_guardrails_test.go | 42 +++++++++++++++++++ internal/monitoring/multi_tenant_monitor.go | 13 ++++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md index 8f23acdbb..5d52a937d 100644 --- a/docs/release-control/v6/internal/subsystems/monitoring.md +++ b/docs/release-control/v6/internal/subsystems/monitoring.md @@ -2726,6 +2726,13 @@ would mutate. Distinct rows are retained so Agent Doctor does not disguise two installations as one healthy machine. Mock mode has no authoritative token inventory for its synthetic hosts and therefore must not turn fixture token IDs into missing-credential incidents. + +The default-org monitor retains the canonical server configuration pointer +rather than a tenant-isolation copy, so tokens minted after startup become part +of that current inventory immediately and cannot produce a false +`agent_credential_missing` diagnosis. `MultiTenantMonitor` deep-copies only +non-default tenant configuration; those tenant copies remain isolated from the +primary runtime's mutable token state. `internal/fleethealth/agent_test.go` and `internal/monitoring/agent_fleet_doctor_test.go` are the focused runtime proofs. diff --git a/internal/monitoring/canonical_guardrails_test.go b/internal/monitoring/canonical_guardrails_test.go index f606b98f2..bdd0c8004 100644 --- a/internal/monitoring/canonical_guardrails_test.go +++ b/internal/monitoring/canonical_guardrails_test.go @@ -2630,6 +2630,48 @@ func TestTenantMonitorWiresOrgIdentityIntoNotifications(t *testing.T) { } } +func TestDefaultOrgMonitorSharesCanonicalRuntimeTokenInventory(t *testing.T) { + dataDir := t.TempDir() + baseCfg := &config.Config{DataPath: dataDir, ConfigPath: dataDir} + mtm := NewMultiTenantMonitor(baseCfg, config.NewMultiTenantPersistence(dataDir), nil) + t.Cleanup(mtm.Stop) + + monitor, err := mtm.GetMonitor("default") + if err != nil { + t.Fatalf("GetMonitor(default) error = %v", err) + } + if monitor.GetConfig() != baseCfg { + t.Fatal("default monitor must retain the canonical server config pointer") + } + + now := time.Now().UTC() + config.Mu.Lock() + baseCfg.APITokens = []config.APITokenRecord{{ + ID: "fresh-agent-token", + Name: "Fresh agent token", + CreatedAt: now, + Scopes: []string{config.ScopeAgentExec}, + }} + config.Mu.Unlock() + monitor.mu.Lock() + monitor.state.Hosts = []models.Host{{ + ID: "agent-fresh-token", + Hostname: "fresh-token-host", + Status: "online", + LastSeen: now, + AgentVersion: "6.2.2", + TokenID: "fresh-agent-token", + CommandsEnabled: true, + }} + monitor.mu.Unlock() + + diagnostics := monitor.GetAgentFleetDiagnostics("6.2.2", now) + agent := requireAgentDiagnostic(t, diagnostics, "agent-agent-fresh-token") + if diagnosticHasAnyReason(agent.Reasons, AgentFleetReasonCredentialMissing, AgentFleetReasonExecScopeMissing) { + t.Fatalf("fresh canonical token was diagnosed as unavailable: %+v", agent.Reasons) + } +} + // Instance-wide notification settings (webhook security allowlist, public // URL) propagate through ForEachMonitor; it must visit every live tenant // monitor so no org's manager is left observing stale security settings. diff --git a/internal/monitoring/multi_tenant_monitor.go b/internal/monitoring/multi_tenant_monitor.go index 7fd6a372b..14a76d656 100644 --- a/internal/monitoring/multi_tenant_monitor.go +++ b/internal/monitoring/multi_tenant_monitor.go @@ -184,10 +184,15 @@ func (mtm *MultiTenantMonitor) GetMonitor(orgID string) (*Monitor, error) { log.Info().Str("org_id", orgID).Msg("initializing tenant monitor") // 1. Load Tenant Config - // Deep copy the base config to ensure tenant isolation. - // Each tenant gets its own independent config that won't share - // credential slices or other mutable state with other tenants. - tenantConfig := mtm.baseConfig.DeepCopy() + // The default org is the primary runtime and must retain the canonical + // configuration pointer owned by the server. Runtime auth mutations such as + // freshly minted agent tokens need to become visible to monitoring-backed + // diagnostics immediately. Non-default tenants remain isolated deep copies + // so their mutable state and credentials cannot alias the primary runtime. + tenantConfig := mtm.baseConfig + if orgID != "default" { + tenantConfig = mtm.baseConfig.DeepCopy() + } // Clear inherited credentials - tenants must load their own // This prevents credential leakage between tenants