mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:11:29 +00:00
fix(audit): drain in-flight recording goroutines on shutdown (M-1)
Audit events spawned from the HTTP middleware ran in detached goroutines using context.Background(). On SIGTERM the DB pool was closed before those goroutines finished writing, silently dropping audit events (CWE-662 Improper Synchronization / CWE-400 Uncontrolled Resource Consumption). NewAuditLog now returns an *AuditMiddleware struct that tracks every spawned goroutine with sync.WaitGroup. Callers wire the middleware via its Middleware method value (preserves the existing func(http.Handler) http.Handler shape) and drain the WaitGroup with Flush(ctx), which blocks until in-flight recordings complete or the provided context is cancelled — mirroring scheduler.WaitForCompletion. Flush is invoked in cmd/server/main.go between http.Server.Shutdown (no new requests accepted) and db.Close (pool torn down), with a timeout returning ErrAuditFlushTimeout wrapping ctx.Err(). Request-derived inputs (method, path, status) are snapshotted before the goroutine spawn so the worker does not race with http.Server reusing r after the handler returns. Tests: TestAuditLog_FlushDrainsInFlightGoroutines TestAuditLog_FlushTimeoutReturnsErrAuditFlushTimeout Verification: go build ./... : 0 go vet ./... : 0 go test -race -short ./... : 0 (all packages) go test -cover ./internal/api/middleware : 81.4% golangci-lint run : 0 issues govulncheck ./... : 0 vulns in called code
This commit is contained in:
+13
-2
@@ -589,7 +589,7 @@ func main() {
|
||||
bodyLimitMiddleware,
|
||||
corsMiddleware,
|
||||
authMiddleware,
|
||||
auditMiddleware,
|
||||
auditMiddleware.Middleware,
|
||||
}
|
||||
|
||||
// Add rate limiter if enabled
|
||||
@@ -606,7 +606,7 @@ func main() {
|
||||
rateLimiter,
|
||||
corsMiddleware,
|
||||
authMiddleware,
|
||||
auditMiddleware,
|
||||
auditMiddleware.Middleware,
|
||||
}
|
||||
logger.Info("rate limiting enabled", "rps", cfg.RateLimit.RPS, "burst", cfg.RateLimit.BurstSize)
|
||||
}
|
||||
@@ -724,6 +724,17 @@ func main() {
|
||||
logger.Error("HTTP server shutdown error", "error", err)
|
||||
}
|
||||
|
||||
// Drain in-flight audit-recording goroutines before closing the DB pool.
|
||||
// The audit middleware spawns one goroutine per non-excluded request; those
|
||||
// goroutines run detached from the request context and write to the
|
||||
// audit_events table via the same *sql.DB. Without this drain, SIGTERM
|
||||
// would close the DB pool while recordings were mid-flight, silently
|
||||
// dropping audit events (M-1, CWE-662 / CWE-400).
|
||||
logger.Info("flushing audit middleware in-flight recordings")
|
||||
if err := auditMiddleware.Flush(shutdownCtx); err != nil {
|
||||
logger.Warn("audit middleware flush did not complete in time", "error", err)
|
||||
}
|
||||
|
||||
// Close database connection
|
||||
if err := db.Close(); err != nil {
|
||||
logger.Error("error closing database connection", "error", err)
|
||||
|
||||
Reference in New Issue
Block a user