mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-26 05:29:45 +00:00
111a62cfe8
POST /api/agents/agent/uninstall (and its host alias) is a collector's self-removal: a token holding agent:report removes the agent that token owns. For any caller that is not a token, the handler instead takes the administrative removal path and deletes any host agent by ID. The route was only RequireAuth plus RequireScope(agent:report), and browser sessions bypass token scopes, so a non-admin SSO, proxy or organization member session could remove any agent, while the equivalent unlink and delete routes require an admin. The route now uses RequireAdmin like its neighbours. Token callers are unchanged: RequireAdmin admits them and the agent:report scope check still applies, so collector and install.ps1 self-uninstall keep working. The existing non-admin proxy test gains the two uninstall routes, and a new test keeps the token path reachable and the scope check in place. Contract-Neutral: Behavioural fix that gives the administrative removal path the admin gate its sibling routes have; no new API surface or contract.
61 lines
2.4 KiB
Go
61 lines
2.4 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
)
|
|
|
|
func TestProxyAuthAdminGatesAdminEndpoints(t *testing.T) {
|
|
t.Setenv("PULSE_DEV", "true")
|
|
|
|
record := newTokenRecord(t, "proxy-admin-gate-token-123.12345678", []string{config.ScopeSettingsRead}, nil)
|
|
cfg := newTestConfigWithTokens(t, record)
|
|
cfg.ProxyAuthSecret = "proxy-secret"
|
|
cfg.ProxyAuthUserHeader = "X-Remote-User"
|
|
cfg.ProxyAuthRoleHeader = "X-Remote-Roles"
|
|
cfg.ProxyAuthAdminRole = "admin"
|
|
|
|
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
|
|
|
|
cases := []struct {
|
|
method string
|
|
path string
|
|
body string
|
|
}{
|
|
{method: http.MethodGet, path: "/api/logs/stream", body: ""},
|
|
{method: http.MethodGet, path: "/api/logs/download", body: ""},
|
|
{method: http.MethodGet, path: "/api/logs/level", body: ""},
|
|
{method: http.MethodPost, path: "/api/logs/level", body: `{"level":"info"}`},
|
|
{method: http.MethodGet, path: "/api/diagnostics", body: ""},
|
|
{method: http.MethodPost, path: "/api/diagnostics/docker/prepare-token", body: `{}`},
|
|
{method: http.MethodGet, path: "/api/system/settings", body: ""},
|
|
{method: http.MethodPost, path: "/api/system/settings/update", body: `{}`},
|
|
{method: http.MethodPost, path: "/api/agents/agent/link", body: `{}`},
|
|
{method: http.MethodPost, path: "/api/agents/agent/unlink", body: `{}`},
|
|
{method: http.MethodPost, path: "/api/agents/host/link", body: `{}`},
|
|
{method: http.MethodPost, path: "/api/agents/host/unlink", body: `{}`},
|
|
{method: http.MethodPost, path: "/api/agents/agent/uninstall", body: `{"agentId":"agent-1"}`},
|
|
{method: http.MethodPost, path: "/api/agents/host/uninstall", body: `{"agentId":"agent-1"}`},
|
|
{method: http.MethodGet, path: "/api/admin/profiles/", body: ""},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
|
req.Header.Set("X-Proxy-Secret", cfg.ProxyAuthSecret)
|
|
req.Header.Set("X-Remote-User", "viewer-user")
|
|
req.Header.Set("X-Remote-Roles", "viewer")
|
|
rec := httptest.NewRecorder()
|
|
router.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403 for non-admin proxy user on %s %s, got %d", tc.method, tc.path, rec.Code)
|
|
}
|
|
if !strings.Contains(rec.Body.String(), "Admin") {
|
|
t.Fatalf("expected admin privilege error for %s %s, got %q", tc.method, tc.path, rec.Body.String())
|
|
}
|
|
}
|
|
}
|