@@ -108,43 +108,43 @@ func metricsMiddleware(next http.Handler) http.Handler {
108108// Start starts the HTTP server, cron scheduler, and webhook handler.
109109// It blocks until the context is cancelled.
110110func (s * Server ) Start (ctx context.Context ) error {
111- mux := http . NewServeMux ()
112-
113- // Prometheus metrics endpoint .
114- mux . Handle ( "/metrics" , promhttp . Handler () )
111+ // apiMux holds all routes that require authentication (and rate limiting).
112+ // Health and metrics endpoints are registered on the top-level mux below
113+ // so that Kubernetes probes and Prometheus scrapes bypass auth entirely .
114+ apiMux := http . NewServeMux ( )
115115
116116 // Git push webhook endpoint — registered before the generic /hooks/ prefix
117117 // so the more-specific path is explicit (Go's ServeMux uses longest-prefix
118118 // match regardless of registration order, but explicit ordering is clearer).
119- mux .Handle ("/hooks/git/" , & GitWebhookHandler {
119+ apiMux .Handle ("/hooks/git/" , & GitWebhookHandler {
120120 DB : s .DB ,
121121 Store : s .RepoStore ,
122122 Driver : s .GitDriver ,
123123 })
124124
125125 // Webhook endpoints.
126- mux .HandleFunc ("/hooks/" , s .webhooks .ServeHTTP )
126+ apiMux .HandleFunc ("/hooks/" , s .webhooks .ServeHTTP )
127127
128128 // API endpoints.
129- mux .HandleFunc ("POST /api/v1/run/{workflow}" , s .handleRun )
130- mux .HandleFunc ("POST /api/v1/cancel/{execution}" , s .handleCancel )
131- mux .HandleFunc ("GET /api/v1/executions" , s .handleListExecutions )
132- mux .HandleFunc ("GET /api/v1/executions/{id}" , s .handleGetExecution )
129+ apiMux .HandleFunc ("POST /api/v1/run/{workflow}" , s .handleRun )
130+ apiMux .HandleFunc ("POST /api/v1/cancel/{execution}" , s .handleCancel )
131+ apiMux .HandleFunc ("GET /api/v1/executions" , s .handleListExecutions )
132+ apiMux .HandleFunc ("GET /api/v1/executions/{id}" , s .handleGetExecution )
133133
134134 // Workflow definition endpoints.
135- mux .HandleFunc ("GET /api/v1/workflows" , s .handleListWorkflows )
136- mux .HandleFunc ("GET /api/v1/workflows/{name}" , s .handleGetWorkflow )
137- mux .HandleFunc ("GET /api/v1/workflows/{name}/versions" , s .handleListWorkflowVersions )
138- mux .HandleFunc ("GET /api/v1/workflows/{name}/versions/{version}" , s .handleGetWorkflowVersion )
135+ apiMux .HandleFunc ("GET /api/v1/workflows" , s .handleListWorkflows )
136+ apiMux .HandleFunc ("GET /api/v1/workflows/{name}" , s .handleGetWorkflow )
137+ apiMux .HandleFunc ("GET /api/v1/workflows/{name}/versions" , s .handleListWorkflowVersions )
138+ apiMux .HandleFunc ("GET /api/v1/workflows/{name}/versions/{version}" , s .handleGetWorkflowVersion )
139139
140140 // Budget endpoints.
141- mux .HandleFunc ("GET /api/v1/budgets" , s .handleListBudgets )
142- mux .HandleFunc ("PUT /api/v1/budgets/{provider}" , s .handleSetBudget )
143- mux .HandleFunc ("DELETE /api/v1/budgets/{provider}" , s .handleDeleteBudget )
144- mux .HandleFunc ("GET /api/v1/budgets/usage" , s .handleGetUsage )
141+ apiMux .HandleFunc ("GET /api/v1/budgets" , s .handleListBudgets )
142+ apiMux .HandleFunc ("PUT /api/v1/budgets/{provider}" , s .handleSetBudget )
143+ apiMux .HandleFunc ("DELETE /api/v1/budgets/{provider}" , s .handleDeleteBudget )
144+ apiMux .HandleFunc ("GET /api/v1/budgets/usage" , s .handleGetUsage )
145145
146146 // OpenAPI spec endpoint.
147- mux .HandleFunc ("GET /api/v1/openapi.json" , handleOpenAPISpec )
147+ apiMux .HandleFunc ("GET /api/v1/openapi.json" , handleOpenAPISpec )
148148
149149 // Start distributed engine components (worker + reaper).
150150 if cfg := config .FromContext (ctx ); cfg != nil {
@@ -238,24 +238,30 @@ func (s *Server) Start(ctx context.Context) error {
238238 go s .pollQueueDepth (ctx , cfg .Engine .ReaperInterval )
239239 }
240240
241- // Health endpoints. /readyz checks DB connectivity only; worker/reaper
242- // liveness is no longer gated here to avoid flapping between poll cycles.
243- mux .Handle ("/healthz" , health .HealthzHandler ())
244- mux .Handle ("/readyz" , health .ReadyzHandler (s .DB ))
245-
246- // Wrap with metrics middleware (innermost, runs for every request).
247- handler := metricsMiddleware (mux )
241+ // Wrap the API mux with metrics middleware, auth, and rate limiting.
242+ apiHandler := metricsMiddleware (apiMux )
248243 if s .AuthStore != nil {
249244 if s .Auditor != nil {
250- handler = auth .AuthMiddleware (s .AuthStore , s .OIDCValidator , handler , s .Auditor )
245+ apiHandler = auth .AuthMiddleware (s .AuthStore , s .OIDCValidator , apiHandler , s .Auditor )
251246 } else {
252- handler = auth .AuthMiddleware (s .AuthStore , s .OIDCValidator , handler )
247+ apiHandler = auth .AuthMiddleware (s .AuthStore , s .OIDCValidator , apiHandler )
253248 }
254249 }
255-
256- // Apply rate limiting (after auth so rate limit keys can use API key prefix).
257250 rl := NewRateLimiter (100 , 200 )
258- handler = rl .Middleware (handler )
251+ apiHandler = rl .Middleware (apiHandler )
252+
253+ // Top-level mux: health and metrics endpoints bypass auth so that Kubernetes
254+ // probes and Prometheus scrapers work without an API key. All other requests
255+ // fall through to the auth-wrapped apiHandler.
256+ mux := http .NewServeMux ()
257+ mux .Handle ("/metrics" , promhttp .Handler ())
258+ mux .Handle ("/healthz" , health .HealthzHandler ())
259+ // /readyz checks DB connectivity only; worker/reaper liveness is intentionally
260+ // excluded to prevent flapping between poll cycles.
261+ mux .Handle ("/readyz" , health .ReadyzHandler (s .DB ))
262+ mux .Handle ("/" , apiHandler )
263+
264+ handler := mux
259265
260266 s .httpServer = & http.Server {
261267 Addr : s .Address ,
0 commit comments