Require proxy admin for AI test endpoints

This commit is contained in:
rcourtman 2026-02-04 16:30:22 +00:00
parent 631a59465c
commit 25285e64bc
2 changed files with 75 additions and 0 deletions

View file

@ -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 {

View file

@ -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)