diff --git a/cmd/server/main.go b/cmd/server/main.go index 568583f..0acd40c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -329,7 +329,12 @@ func main() { // counters get wired in Phase 8 when the Prometheus exposer reads // them. ocspResponseCacheRepo := postgres.NewOCSPResponseCacheRepository(db) - ocspResponseCacheService := service.NewOCSPResponseCacheService(ocspResponseCacheRepo, caOperationsSvc, nil, logger) + // Production hardening II Phase 8: share a single OCSPCounters + // instance between the cache service (Phase 2) and the Prometheus + // exposer (Phase 8) so the metrics endpoint reflects every counter + // tick that happens inside the cache service's hot path. + ocspCounters := service.NewOCSPCounters() + ocspResponseCacheService := service.NewOCSPResponseCacheService(ocspResponseCacheRepo, caOperationsSvc, ocspCounters, logger) caOperationsSvc.SetOCSPCacheSvc(ocspResponseCacheService) // Load-bearing security wire: invalidate the cache after a successful // revocation so the next OCSP fetch returns "revoked" (not the stale @@ -524,6 +529,11 @@ func main() { notificationHandler := handler.NewNotificationHandler(notificationService) statsHandler := handler.NewStatsHandler(statsService) metricsHandler := handler.NewMetricsHandler(statsService, time.Now()) + // Production hardening II Phase 8: wire the per-area counter + // snapshotters so the Prometheus exposer surfaces them. Operators + // alert on certctl_ocsp_counter_total{label="rate_limited"}, + // {label="nonce_malformed"}, etc. + metricsHandler.SetOCSPCounters(ocspCounters) // Bundle-5 / H-006: pass the *sql.DB pool so /ready can probe DB // connectivity via PingContext. /health stays shallow (liveness signal). healthHandler := handler.NewHealthHandler(cfg.Auth.Type, db) diff --git a/internal/api/handler/metrics.go b/internal/api/handler/metrics.go index ccc1909..8b7040a 100644 --- a/internal/api/handler/metrics.go +++ b/internal/api/handler/metrics.go @@ -15,12 +15,27 @@ type MetricsService interface { GetDashboardSummary(ctx context.Context) (interface{}, error) } +// CounterSnapshotter is the minimum surface MetricsHandler consumes +// from a counter table for the Prometheus exposer. The OCSPCounters +// type in internal/service satisfies this; future per-area counter +// tabs (CRL, cert-export, EST, SCEP, Intune) plug in the same way. +// +// Production hardening II Phase 8. +type CounterSnapshotter interface { + Snapshot() map[string]uint64 +} + // MetricsHandler handles HTTP requests for metrics. // Supports both JSON format (GET /api/v1/metrics) and Prometheus exposition format // (GET /api/v1/metrics/prometheus) for integration with Prometheus, Grafana, Datadog, etc. type MetricsHandler struct { svc MetricsService serverStarted time.Time + // Production hardening II Phase 8 — per-area counter snapshotters. + // nil values omit the corresponding metric block; cmd/server/main.go + // wires the instances at startup. The naming convention is + // certctl__