From 04a828264a76cc20c2871319599e09250a932bb3 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 30 Mar 2026 19:30:48 +0100 Subject: [PATCH] Restore legacy host config fetch compatibility (#1254) --- .../host_agent_config_binding_router_test.go | 34 +++++++++++++++++++ internal/api/host_agents.go | 3 ++ internal/api/host_agents_test.go | 34 ++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/internal/api/host_agent_config_binding_router_test.go b/internal/api/host_agent_config_binding_router_test.go index fc8ff56a4..3e8b0d74c 100644 --- a/internal/api/host_agent_config_binding_router_test.go +++ b/internal/api/host_agent_config_binding_router_test.go @@ -44,3 +44,37 @@ func TestHostAgentConfigUsesTokenBindingInRouter(t *testing.T) { t.Fatalf("expected host id %q, got %q", "host-1", resp.HostID) } } + +func TestHostAgentConfigAllowsLegacyHostReportScopeInRouter(t *testing.T) { + rawToken := "host-config-legacy-token-123.12345678" + record := newTokenRecord(t, rawToken, []string{config.ScopeHostReport}, nil) + record.ID = "token-1" + + cfg := newTestConfigWithTokens(t, record) + + state := models.NewState() + state.UpsertHost(models.Host{ID: "host-1", TokenID: "token-1"}) + monitor := &monitoring.Monitor{} + setUnexportedField(t, monitor, "state", state) + + router := NewRouter(cfg, monitor, nil, nil, nil, "1.0.0") + + req := httptest.NewRequest(http.MethodGet, "/api/agents/host/host-2/config", nil) + req.Header.Set("X-API-Token", rawToken) + rec := httptest.NewRecorder() + router.Handler().ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected 200 for legacy bound host config, got %d", rec.Code) + } + + var resp struct { + HostID string `json:"hostId"` + } + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if resp.HostID != "host-1" { + t.Fatalf("expected host id %q, got %q", "host-1", resp.HostID) + } +} diff --git a/internal/api/host_agents.go b/internal/api/host_agents.go index 42d3b7b19..5cd4c3c17 100644 --- a/internal/api/host_agents.go +++ b/internal/api/host_agents.go @@ -321,6 +321,9 @@ func (h *HostAgentHandlers) canReadConfig(record *config.APITokenRecord) bool { return true } return record.HasScope(config.ScopeHostConfigRead) || + // Older host-agent tokens may only carry host-agent:report. + // Allow them to continue fetching their own config via token binding. + record.HasScope(config.ScopeHostReport) || record.HasScope(config.ScopeHostManage) || record.HasScope(config.ScopeSettingsWrite) } diff --git a/internal/api/host_agents_test.go b/internal/api/host_agents_test.go index f70b96818..fdf50e45d 100644 --- a/internal/api/host_agents_test.go +++ b/internal/api/host_agents_test.go @@ -325,7 +325,7 @@ func TestHandleConfigMissingConfigScope(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/agents/host/"+hostID+"/config", nil) attachAPITokenRecord(req, &config.APITokenRecord{ ID: "token-other", - Scopes: []string{config.ScopeHostReport}, + Scopes: []string{config.ScopeMonitoringRead}, }) rec := httptest.NewRecorder() @@ -336,6 +336,38 @@ func TestHandleConfigMissingConfigScope(t *testing.T) { } } +func TestHandleConfigAllowsBoundHostReportScope(t *testing.T) { + t.Parallel() + + handler := newHostAgentHandlerForTests(t, models.Host{ + ID: "host-1", + TokenID: "token-expected", + }) + + req := httptest.NewRequest(http.MethodGet, "/api/agents/host/other-host/config", nil) + attachAPITokenRecord(req, &config.APITokenRecord{ + ID: "token-expected", + Scopes: []string{config.ScopeHostReport}, + }) + + rec := httptest.NewRecorder() + handler.HandleConfig(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) + } + + var resp struct { + HostID string `json:"hostId"` + } + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("failed to decode response: %v", err) + } + if resp.HostID != "host-1" { + t.Fatalf("expected host id %q, got %q", "host-1", resp.HostID) + } +} + func TestHandleConfigUsesTokenBinding(t *testing.T) { t.Parallel()