fix(security): A-3 auth middleware coverage fixes

Fixes 9 auth middleware findings from the A-3 recon audit.

F-A3-11 CRITICAL: Removed JWT secret from WebAuthMiddleware log output.
  Replaced emoji-prefixed fmt.Printf with ETHOS-compliant log.Printf.
  No secret values in any log output.

F-A3-7 CRITICAL: Config download now requires WebAuthMiddleware.
  GET /downloads/config/:agent_id is admin-only (agents never call it).

F-A3-6 HIGH: Update package download now requires AuthMiddleware.
  GET /downloads/updates/:package_id requires valid agent JWT.

F-A3-10 HIGH: Scheduler stats changed from AuthMiddleware to
  WebAuthMiddleware. Agent JWTs can no longer view scheduler internals.

F-A3-13 LOW: RequireAdmin() middleware implemented. 7 security settings
  routes re-enabled (GET/PUT/POST under /security/settings).
  security_settings.go.broken renamed to .go, API mismatches fixed.

F-A3-12 MEDIUM: JWT issuer claims added for token type separation.
  Agent tokens: issuer=redflag-agent, Web tokens: issuer=redflag-web.
  AuthMiddleware rejects tokens with wrong issuer.
  Grace period: tokens with no issuer still accepted (backward compat).

F-A3-2 MEDIUM: /auth/verify now has WebAuthMiddleware applied.
  Endpoint returns 200 with valid=true for valid admin tokens.

F-A3-9 MEDIUM: Agent self-unregister (DELETE /:id) now rate-limited
  using the same agent_reports rate limiter as other agent routes.

F-A3-14 LOW: CORS origin configurable via REDFLAG_CORS_ORIGIN env var.
  Defaults to http://localhost:3000 for development.
  Added PATCH method and agent-specific headers to CORS config.

All 27 server tests pass. All 14 agent tests pass. No regressions.
See docs/A3_Fix_Implementation.md and docs/Deviations_Report.md
(DEV-020 through DEV-022).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 22:17:40 -04:00
parent ee246771dc
commit 4c62de8d8b
15 changed files with 620 additions and 145 deletions

View File

@@ -365,6 +365,13 @@ func main() {
} else {
log.Printf("[OK] Security settings service initialized")
}
// Initialize security settings handler (F-A3-13: re-enable security settings routes)
var securitySettingsHandler *handlers.SecuritySettingsHandler
if securitySettingsService != nil {
securitySettingsHandler = handlers.NewSecuritySettingsHandler(securitySettingsService)
}
// Setup router
router := gin.Default()
@@ -385,7 +392,7 @@ func main() {
// Authentication routes (with rate limiting)
api.POST("/auth/login", rateLimiter.RateLimit("public_access", middleware.KeyByIP), authHandler.Login)
api.POST("/auth/logout", authHandler.Logout)
api.GET("/auth/verify", authHandler.VerifyToken)
api.GET("/auth/verify", authHandler.WebAuthMiddleware(), authHandler.VerifyToken)
// Public system routes (no authentication required)
api.GET("/public-key", rateLimiter.RateLimit("public_access", middleware.KeyByIP), systemHandler.GetPublicKey)
@@ -406,11 +413,13 @@ func main() {
buildRoutes.POST("/detect", rateLimiter.RateLimit("agent_build", middleware.KeyByAgentID), handlers.DetectAgentInstallation)
}
// Public download routes (no authentication - agents need these!)
// Public download routes (agent binaries and install scripts remain public for bootstrapping)
api.GET("/downloads/:platform", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.DownloadAgent)
api.GET("/downloads/updates/:package_id", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.DownloadUpdatePackage)
api.GET("/downloads/config/:agent_id", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.HandleConfigDownload)
api.GET("/install/:platform", rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.InstallScript)
// Protected download routes (F-A3-6, F-A3-7: require authentication)
api.GET("/downloads/updates/:package_id", middleware.AuthMiddleware(), rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.DownloadUpdatePackage)
api.GET("/downloads/config/:agent_id", authHandler.WebAuthMiddleware(), rateLimiter.RateLimit("public_access", middleware.KeyByIP), downloadHandler.HandleConfigDownload)
}
// Start background goroutine to mark offline agents
@@ -476,7 +485,7 @@ func main() {
}
verificationHandler.VerifySignature(c)
})
agents.DELETE("/:id", agentHandler.UnregisterAgent)
agents.DELETE("/:id", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), agentHandler.UnregisterAgent)
// New dedicated endpoints for metrics and docker images (data classification fix)
agents.POST("/:id/metrics", rateLimiter.RateLimit("agent_reports", middleware.KeyByAgentID), metricsHandler.ReportMetrics)
@@ -597,17 +606,20 @@ func main() {
dashboard.GET("/security/metrics", securityHandler.SecurityMetrics)
// Security Settings Management endpoints (admin-only)
// securitySettings := dashboard.Group("/security/settings")
// securitySettings.Use(middleware.RequireAdmin())
// {
// securitySettings.GET("", securitySettingsHandler.GetAllSecuritySettings)
// securitySettings.GET("/audit", securitySettingsHandler.GetSecurityAuditTrail)
// securitySettings.GET("/overview", securitySettingsHandler.GetSecurityOverview)
// securitySettings.GET("/:category", securitySettingsHandler.GetSecuritySettingsByCategory)
// securitySettings.PUT("/:category/:key", securitySettingsHandler.UpdateSecuritySetting)
// securitySettings.POST("/validate", securitySettingsHandler.ValidateSecuritySettings)
// securitySettings.POST("/apply", securitySettingsHandler.ApplySecuritySettings)
// }
// F-A3-13 fix: RequireAdmin() middleware implemented, routes re-enabled
if securitySettingsHandler != nil {
securitySettings := dashboard.Group("/security/settings")
securitySettings.Use(middleware.RequireAdmin())
{
securitySettings.GET("", securitySettingsHandler.GetAllSecuritySettings)
securitySettings.GET("/audit", securitySettingsHandler.GetSecurityAuditTrail)
securitySettings.GET("/overview", securitySettingsHandler.GetSecurityOverview)
securitySettings.GET("/:category", securitySettingsHandler.GetSecuritySettingsByCategory)
securitySettings.PUT("/:category/:key", securitySettingsHandler.UpdateSecuritySetting)
securitySettings.POST("/validate", securitySettingsHandler.ValidateSecuritySettings)
securitySettings.POST("/apply", securitySettingsHandler.ApplySecuritySettings)
}
}
}
// Load subsystems into queue
@@ -624,7 +636,8 @@ func main() {
}
// Add scheduler stats endpoint (after scheduler is initialized)
router.GET("/api/v1/scheduler/stats", middleware.AuthMiddleware(), func(c *gin.Context) {
// F-A3-10 fix: use WebAuthMiddleware (admin only), not AuthMiddleware (agent JWT)
router.GET("/api/v1/scheduler/stats", authHandler.WebAuthMiddleware(), func(c *gin.Context) {
stats := subsystemScheduler.GetStats()
queueStats := subsystemScheduler.GetQueueStats()
c.JSON(200, gin.H{