mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 03:20:11 +00:00
Require proxy admin for AI test endpoints
This commit is contained in:
parent
631a59465c
commit
25285e64bc
2 changed files with 75 additions and 0 deletions
|
|
@ -1498,6 +1498,22 @@ func (h *AISettingsHandler) HandleTestAIConnection(w http.ResponseWriter, r *htt
|
|||
return
|
||||
}
|
||||
|
||||
// Check proxy auth admin status if applicable
|
||||
if h.getConfig(r.Context()).ProxyAuthSecret != "" {
|
||||
if valid, username, isAdmin := CheckProxyAuth(h.getConfig(r.Context()), r); valid && !isAdmin {
|
||||
log.Warn().
|
||||
Str("ip", r.RemoteAddr).
|
||||
Str("path", r.URL.Path).
|
||||
Str("method", r.Method).
|
||||
Str("username", username).
|
||||
Msg("Non-admin user attempted AI connection test")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Admin privileges required"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -1537,6 +1553,22 @@ func (h *AISettingsHandler) HandleTestProvider(w http.ResponseWriter, r *http.Re
|
|||
return
|
||||
}
|
||||
|
||||
// Check proxy auth admin status if applicable
|
||||
if h.getConfig(r.Context()).ProxyAuthSecret != "" {
|
||||
if valid, username, isAdmin := CheckProxyAuth(h.getConfig(r.Context()), r); valid && !isAdmin {
|
||||
log.Warn().
|
||||
Str("ip", r.RemoteAddr).
|
||||
Str("path", r.URL.Path).
|
||||
Str("method", r.Method).
|
||||
Str("username", username).
|
||||
Msg("Non-admin user attempted AI provider test")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": "Admin privileges required"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get provider from URL path (e.g., /api/ai/test/anthropic -> anthropic)
|
||||
provider := strings.TrimPrefix(r.URL.Path, "/api/ai/test/")
|
||||
if provider == "" || provider == r.URL.Path {
|
||||
|
|
|
|||
|
|
@ -2028,6 +2028,7 @@ func TestProxyAuthNonAdminDeniedAdminEndpoints(t *testing.T) {
|
|||
cfg.ProxyAuthAdminRole = "admin"
|
||||
|
||||
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
|
||||
router.aiSettingsHandler.legacyConfig = cfg
|
||||
|
||||
cases := []struct {
|
||||
method string
|
||||
|
|
@ -2082,6 +2083,8 @@ func TestProxyAuthNonAdminDeniedAdminEndpoints(t *testing.T) {
|
|||
{method: http.MethodPost, path: "/api/ai/oauth/start", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/ai/oauth/exchange", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/ai/oauth/disconnect", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/ai/test", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/ai/test/openai", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/agents/docker/containers/update", body: `{}`},
|
||||
{method: http.MethodDelete, path: "/api/agents/docker/hosts/host-1", body: ``},
|
||||
{method: http.MethodDelete, path: "/api/agents/kubernetes/clusters/cluster-1", body: ``},
|
||||
|
|
@ -2399,6 +2402,46 @@ func TestAISettingsUpdateRejectsProxyNonAdmin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAITestConnectionRejectsProxyNonAdmin(t *testing.T) {
|
||||
cfg := newTestConfigWithTokens(t)
|
||||
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")
|
||||
router.aiSettingsHandler.legacyConfig = cfg
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/ai/test", strings.NewReader(`{}`))
|
||||
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 AI test, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAITestProviderRejectsProxyNonAdmin(t *testing.T) {
|
||||
cfg := newTestConfigWithTokens(t)
|
||||
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")
|
||||
router.aiSettingsHandler.legacyConfig = cfg
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/ai/test/openai", strings.NewReader(`{}`))
|
||||
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 AI provider test, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIChatEndpointsRequireAIChatScope(t *testing.T) {
|
||||
rawToken := "ai-chat-token-123.12345678"
|
||||
record := newTokenRecord(t, rawToken, []string{config.ScopeMonitoringRead}, nil)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue